From 0679d7e0bdd3baeb3e2e86ecb6ec1b0e002cc553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 21 Mar 2024 16:41:55 +0100 Subject: [PATCH] Remove unused spsdk code --- .../bootloader/lpc55_upload/__init__.py | 1 - .../bootloader/lpc55_upload/crypto/cmac.py | 42 - .../bootloader/lpc55_upload/crypto/cms.py | 169 - .../bootloader/lpc55_upload/crypto/keys.py | 250 -- .../bootloader/lpc55_upload/crypto/oscca.py | 149 - .../lpc55_upload/crypto/signature_provider.py | 8 - .../bootloader/lpc55_upload/ele/__init__.py | 8 - .../bootloader/lpc55_upload/ele/ele_comm.py | 291 -- .../lpc55_upload/ele/ele_constants.py | 428 -- .../lpc55_upload/ele/ele_message.py | 1585 ------- .../lpc55_upload/image/ahab/__init__.py | 8 - .../image/ahab/ahab_abstract_interfaces.py | 235 - .../lpc55_upload/image/ahab/ahab_container.py | 3885 ----------------- .../lpc55_upload/image/ahab/signed_msg.py | 1623 ------- .../lpc55_upload/image/ahab/utils.py | 86 - .../bootloader/lpc55_upload/image/header.py | 197 - .../bootloader/lpc55_upload/image/misc.py | 110 - .../bootloader/lpc55_upload/image/secret.py | 951 ---- .../bootloader/lpc55_upload/mboot/__init__.py | 20 +- .../lpc55_upload/mboot/interfaces/buspal.py | 566 --- .../lpc55_upload/mboot/interfaces/sdio.py | 172 - .../lpc55_upload/mboot/interfaces/uart.py | 110 - .../lpc55_upload/mboot/interfaces/usbsio.py | 110 - .../mboot/protocol/serial_protocol.py | 334 -- .../bootloader/lpc55_upload/mboot/scanner.py | 103 - .../lpc55_upload/sbfile/sb2/images.py | 376 +- .../bootloader/lpc55_upload/uboot/__init__.py | 7 - .../bootloader/lpc55_upload/uboot/uboot.py | 137 - .../lpc55_upload/utils/crypto/iee.py | 838 ---- .../lpc55_upload/utils/crypto/otfad.py | 658 --- .../lpc55_upload/utils/crypto/rot.py | 239 - .../bootloader/lpc55_upload/utils/images.py | 616 --- .../utils/interfaces/device/sdio_device.py | 271 -- .../utils/interfaces/device/serial_device.py | 208 - .../utils/interfaces/device/usbsio_device.py | 460 -- .../utils/interfaces/scanner_helper.py | 37 - .../lpc55_upload/utils/registers.py | 1373 ------ 37 files changed, 2 insertions(+), 16659 deletions(-) delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/crypto/cmac.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/crypto/cms.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/crypto/oscca.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/ele/__init__.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_comm.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_constants.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_message.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/__init__.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_abstract_interfaces.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_container.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/signed_msg.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/utils.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/header.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/misc.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/image/secret.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/buspal.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/sdio.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/uart.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/usbsio.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/protocol/serial_protocol.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/mboot/scanner.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/uboot/__init__.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/uboot/uboot.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/iee.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/rot.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/images.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/sdio_device.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/serial_device.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/usbsio_device.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/scanner_helper.py delete mode 100644 pynitrokey/trussed/bootloader/lpc55_upload/utils/registers.py diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/__init__.py b/pynitrokey/trussed/bootloader/lpc55_upload/__init__.py index 9a900c6f..e2c02c55 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/__init__.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/__init__.py @@ -11,7 +11,6 @@ import os __author__ = "NXP" -__contact__ = "michal.starecek@nxp.com" __license__ = "BSD-3-Clause" __version__ = version __release__ = "beta" diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cmac.py b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cmac.py deleted file mode 100644 index 08157087..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cmac.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019-2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""OpenSSL implementation for CMAC packet authentication.""" - -# Used security modules -from cryptography.exceptions import InvalidSignature -from cryptography.hazmat.primitives import cmac as cmac_cls -from cryptography.hazmat.primitives.ciphers import algorithms - - -def cmac(key: bytes, data: bytes) -> bytes: - """Return a CMAC from data with specified key and algorithm. - - :param key: The key in bytes format - :param data: Input data in bytes format - :return: CMAC bytes - """ - cmac_obj = cmac_cls.CMAC(algorithm=algorithms.AES(key)) - cmac_obj.update(data) - return cmac_obj.finalize() - - -def cmac_validate(key: bytes, data: bytes, signature: bytes) -> bool: - """Return a CMAC from data with specified key and algorithm. - - :param key: The key in bytes format - :param data: Input data in bytes format - :param signature: CMAC signature to validate - :return: CMAC bytes - """ - cmac_obj = cmac_cls.CMAC(algorithm=algorithms.AES(key)) - cmac_obj.update(data) - try: - cmac_obj.verify(signature=signature) - return True - except InvalidSignature: - return False diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cms.py b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cms.py deleted file mode 100644 index 2d2db420..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/cms.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""ASN1Crypto implementation for CMS signature container.""" - - -# Used security modules -from datetime import datetime -from typing import Optional - -from ..crypto.certificate import Certificate -from ..crypto.hash import EnumHashAlgorithm, get_hash -from ..crypto.keys import ECDSASignature, PrivateKey, PrivateKeyEcc, PrivateKeyRsa -from ..crypto.signature_provider import SignatureProvider -from ..crypto.types import SPSDKEncoding -from ..exceptions import SPSDKError, SPSDKTypeError, SPSDKValueError - - -def cms_sign( - zulu: datetime, - data: bytes, - certificate: Certificate, - signing_key: Optional[PrivateKey], - signature_provider: Optional[SignatureProvider], -) -> bytes: - """Sign provided data and return CMS signature. - - :param zulu: current UTC time+date - :param data: to be signed - :param certificate: Certificate with issuer information - :param signing_key: Signing key, is mutually exclusive with signature_provider parameter - :param signature_provider: Signature provider, is mutually exclusive with signing_key parameter - :return: CMS signature (binary) - :raises SPSDKError: If certificate is not present - :raises SPSDKError: If private key is not present - :raises SPSDKError: If incorrect time-zone" - """ - # Lazy imports are used here to save some time during SPSDK startup - from asn1crypto import cms, util, x509 - - if certificate is None: - raise SPSDKValueError("Certificate is not present") - if not (signing_key or signature_provider): - raise SPSDKValueError("Private key or signature provider is not present") - if signing_key and signature_provider: - raise SPSDKValueError( - "Only one of private key and signature provider must be specified" - ) - if signing_key and not isinstance(signing_key, (PrivateKeyEcc, PrivateKeyRsa)): - raise SPSDKTypeError(f"Unsupported private key type {type(signing_key)}.") - - # signed data (main section) - signed_data = cms.SignedData() - signed_data["version"] = "v1" - signed_data["encap_content_info"] = util.OrderedDict([("content_type", "data")]) - signed_data["digest_algorithms"] = [ - util.OrderedDict([("algorithm", "sha256"), ("parameters", None)]) - ] - - # signer info sub-section - signer_info = cms.SignerInfo() - signer_info["version"] = "v1" - signer_info["digest_algorithm"] = util.OrderedDict( - [("algorithm", "sha256"), ("parameters", None)] - ) - signer_info["signature_algorithm"] = ( - util.OrderedDict([("algorithm", "rsassa_pkcs1v15"), ("parameters", b"")]) - if (signing_key and isinstance(signing_key, PrivateKeyRsa)) - or (signature_provider and signature_provider.signature_length >= 256) - else util.OrderedDict([("algorithm", "sha256_ecdsa")]) - ) - # signed identifier: issuer amd serial number - - asn1_cert = x509.Certificate.load(certificate.export(SPSDKEncoding.DER)) - signer_info["sid"] = cms.SignerIdentifier( - { - "issuer_and_serial_number": cms.IssuerAndSerialNumber( - { - "issuer": asn1_cert.issuer, - "serial_number": asn1_cert.serial_number, - } - ) - } - ) - # signed attributes - signed_attrs = cms.CMSAttributes() - signed_attrs.append( - cms.CMSAttribute( - { - "type": "content_type", - "values": [cms.ContentType("data")], - } - ) - ) - - # check time-zone is assigned (expected UTC+0) - if not zulu.tzinfo: - raise SPSDKError("Incorrect time-zone") - signed_attrs.append( - cms.CMSAttribute( - { - "type": "signing_time", - "values": [ - cms.Time(name="utc_time", value=zulu.strftime("%y%m%d%H%M%SZ")) - ], - } - ) - ) - signed_attrs.append( - cms.CMSAttribute( - { - "type": "message_digest", - "values": [cms.OctetString(get_hash(data))], # digest - } - ) - ) - signer_info["signed_attrs"] = signed_attrs - - # create signature - data_to_sign = signed_attrs.dump() - signature = sign_data(data_to_sign, signing_key, signature_provider) - - signer_info["signature"] = signature - # Adding SignerInfo object to SignedData object - signed_data["signer_infos"] = [signer_info] - - # content info - content_info = cms.ContentInfo() - content_info["content_type"] = "signed_data" - content_info["content"] = signed_data - - return content_info.dump() - - -def sign_data( - data_to_sign: bytes, - signing_key: Optional[PrivateKey], - signature_provider: Optional[SignatureProvider], -) -> bytes: - """Sign the data. - - :param data_to_sign: Data to be signed - :param signing_key: Signing key, is mutually exclusive with signature_provider parameter - :param signature_provider: Signature provider, is mutually exclusive with signing_key parameter - """ - assert signing_key or signature_provider - if signing_key and signature_provider: - raise SPSDKValueError( - "Only one of private key and signature provider must be specified" - ) - if signing_key: - return ( - signing_key.sign( - data_to_sign, algorithm=EnumHashAlgorithm.SHA256, der_format=True - ) - if isinstance(signing_key, PrivateKeyEcc) - else signing_key.sign(data_to_sign) - ) - assert signature_provider - signature = signature_provider.get_signature(data_to_sign) - # convert to DER format - if signature_provider.signature_length < 256: - ecdsa_signature = ECDSASignature.parse(signature) - signature = ecdsa_signature.export(encoding=SPSDKEncoding.DER) - return signature diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/keys.py b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/keys.py index 3986e7b5..06ab3dee 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/keys.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/keys.py @@ -38,16 +38,9 @@ from ..utils.abstract import BaseClass from ..utils.misc import Endianness, load_binary, write_file from .hash import EnumHashAlgorithm, get_hash, get_hash_algorithm -from .oscca import IS_OSCCA_SUPPORTED from .rng import rand_below, random_hex from .types import SPSDKEncoding -if IS_OSCCA_SUPPORTED: - from asn1tools import DecodeError # pylint: disable=import-error - from gmssl import sm2 # pylint: disable=import-error - - from .oscca import SM2Encoder, sanitize_pem - def _load_pem_private_key(data: bytes, password: Optional[bytes]) -> Any: """Load PEM Private key. @@ -62,13 +55,6 @@ def _load_pem_private_key(data: bytes, password: Optional[bytes]) -> Any: return _crypto_load_private_key(SPSDKEncoding.PEM, data, password) except (UnsupportedAlgorithm, ValueError) as exc: last_error = exc - if IS_OSCCA_SUPPORTED: - try: - key_data = sanitize_pem(data) - key_set = SM2Encoder().decode_private_key(data=key_data) - return sm2.CryptSM2(private_key=key_set.private, public_key=key_set.public) - except (SPSDKError, DecodeError) as exc: - last_error = exc raise SPSDKError(f"Cannot load PEM private key: {last_error}") @@ -85,12 +71,6 @@ def _load_der_private_key(data: bytes, password: Optional[bytes]) -> Any: return _crypto_load_private_key(SPSDKEncoding.DER, data, password) except (UnsupportedAlgorithm, ValueError) as exc: last_error = exc - if IS_OSCCA_SUPPORTED: - try: - key_set = SM2Encoder().decode_private_key(data=data) - return sm2.CryptSM2(private_key=key_set.private, public_key=key_set.public) - except (SPSDKError, DecodeError) as exc: - last_error = exc raise SPSDKError(f"Cannot load DER private key: {last_error}") @@ -139,13 +119,6 @@ def _load_pem_public_key(data: bytes) -> Any: return crypto_load_pem_public_key(data) except (UnsupportedAlgorithm, ValueError) as exc: last_error = exc - if IS_OSCCA_SUPPORTED: - try: - key_data = sanitize_pem(data) - public_key = SM2Encoder().decode_public_key(data=key_data) - return sm2.CryptSM2(private_key=None, public_key=public_key.public) - except (SPSDKError, DecodeError) as exc: - last_error = exc raise SPSDKError(f"Cannot load PEM public key: {last_error}") @@ -161,12 +134,6 @@ def _load_der_public_key(data: bytes) -> Any: return crypto_load_der_public_key(data) except (UnsupportedAlgorithm, ValueError) as exc: last_error = exc - if IS_OSCCA_SUPPORTED: - try: - public_key = SM2Encoder().decode_public_key(data=data) - return sm2.CryptSM2(private_key=None, public_key=public_key.public) - except (SPSDKError, DecodeError) as exc: - last_error = exc raise SPSDKError(f"Cannot load DER private key: {last_error}") @@ -294,8 +261,6 @@ def parse(cls, data: bytes, password: Optional[str] = None) -> Self: ) if isinstance(private_key, (ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey)): return cls.create(private_key) - if IS_OSCCA_SUPPORTED and isinstance(private_key, sm2.CryptSM2): - return cls.create(private_key) except (ValueError, SPSDKInvalidKeyType) as exc: raise SPSDKError(f"Cannot load private key: ({str(exc)})") from exc raise SPSDKError(f"Unsupported private key: ({str(private_key)})") @@ -312,9 +277,6 @@ def create(cls, key: Any) -> Self: PrivateKeyEcc: ec.EllipticCurvePrivateKey, PrivateKeyRsa: rsa.RSAPrivateKey, } - if IS_OSCCA_SUPPORTED: - SUPPORTED_KEYS[PrivateKeySM2] = sm2.CryptSM2 - for k, v in SUPPORTED_KEYS.items(): if isinstance(key, v): return k(key) @@ -391,8 +353,6 @@ def parse(cls, data: bytes) -> Self: }[SPSDKEncoding.get_file_encodings(data)](data) if isinstance(public_key, (ec.EllipticCurvePublicKey, rsa.RSAPublicKey)): return cls.create(public_key) - if IS_OSCCA_SUPPORTED and isinstance(public_key, sm2.CryptSM2): - return cls.create(public_key) except (ValueError, SPSDKInvalidKeyType) as exc: raise SPSDKError(f"Cannot load public key: ({str(exc)})") from exc raise SPSDKError(f"Unsupported public key: ({str(public_key)})") @@ -426,9 +386,6 @@ def create(cls, key: Any) -> Self: PublicKeyEcc: ec.EllipticCurvePublicKey, PublicKeyRsa: rsa.RSAPublicKey, } - if IS_OSCCA_SUPPORTED: - SUPPORTED_KEYS[PublicKeySM2] = sm2.CryptSM2 - for k, v in SUPPORTED_KEYS.items(): if isinstance(key, v): return k(key) @@ -1124,211 +1081,6 @@ def __str__(self) -> str: return f"ECC ({self.curve}) Public key: \nx({hex(self.x)}) \ny({hex(self.y)})" -# =================================================================================================== -# =================================================================================================== -# -# SM2 Key -# -# =================================================================================================== -# =================================================================================================== -if IS_OSCCA_SUPPORTED: - from .oscca import SM2Encoder, SM2KeySet, SM2PublicKey, sanitize_pem - - class PrivateKeySM2(PrivateKey): - """SPSDK SM2 Private Key.""" - - key: sm2.CryptSM2 - - def __init__(self, key: sm2.CryptSM2) -> None: - """Create SPSDK Key. - - :param key: Only SM2 key is accepted - """ - if not isinstance(key, sm2.CryptSM2): - raise SPSDKInvalidKeyType("The input key is not SM2 type") - self.key = key - - @classmethod - def generate_key(cls) -> Self: - """Generate SM2 Key (private key). - - :return: SM2 private key - """ - key = sm2.CryptSM2(None, "None") - n = int(key.ecc_table["n"], base=16) - prk = rand_below(n) - while True: - puk = key._kg(prk, key.ecc_table["g"]) - if puk[:2] != "04": # PUK cannot start with 04 - break - key.private_key = f"{prk:064x}" - key.public_key = puk - - return cls(key) - - def get_public_key(self) -> "PublicKeySM2": - """Generate public key. - - :return: Public key - """ - return PublicKeySM2( - sm2.CryptSM2(private_key=None, public_key=self.key.public_key) - ) - - def verify_public_key(self, public_key: PublicKey) -> bool: - """Verify public key. - - :param public_key: Public key to verify - :return: True if is in pair, False otherwise - """ - return self.get_public_key() == public_key - - def sign( - self, data: bytes, salt: Optional[str] = None, use_ber: bool = False - ) -> bytes: - """Sign data using SM2 algorithm with SM3 hash. - - :param data: Data to sign. - :param salt: Salt for signature generation, defaults to None. If not specified a random string will be used. - :param use_ber: Encode signature into BER format, defaults to True - :raises SPSDKError: Signature can't be created. - :return: SM2 signature. - """ - data_hash = bytes.fromhex(self.key._sm3_z(data)) - if salt is None: - salt = random_hex(self.key.para_len // 2) - signature_str = self.key.sign(data=data_hash, K=salt) - if not signature_str: - raise SPSDKError("Can't sign data") - signature = bytes.fromhex(signature_str) - if use_ber: - ber_signature = SM2Encoder().encode_signature(signature) - return ber_signature - return signature - - def export( - self, - password: Optional[str] = None, - encoding: SPSDKEncoding = SPSDKEncoding.DER, - ) -> bytes: - """Convert key into bytes supported by NXP.""" - if encoding != SPSDKEncoding.DER: - raise SPSDKNotImplementedError( - "Only DER enocding is supported for SM2 keys export" - ) - keys = SM2KeySet(self.key.private_key, self.key.public_key) - return SM2Encoder().encode_private_key(keys) - - def __repr__(self) -> str: - return "SM2 Private Key" - - def __str__(self) -> str: - """Object description in string format.""" - return f"SM2Key(private_key={self.key.private_key}, public_key='{self.key.public_key}')" - - @property - def key_size(self) -> int: - """Size of the key in bits.""" - return self.key.para_len - - @property - def signature_size(self) -> int: - """Signature size.""" - return 64 - - class PublicKeySM2(PublicKey): - """SM2 Public Key.""" - - key: sm2.CryptSM2 - - def __init__(self, key: sm2.CryptSM2) -> None: - """Create SPSDK Public Key. - - :param key: SPSDK Public Key data or file path - """ - if not isinstance(key, sm2.CryptSM2): - raise SPSDKInvalidKeyType("The input key is not SM2 type") - self.key = key - - def verify_signature( - self, - signature: bytes, - data: bytes, - algorithm: Optional[EnumHashAlgorithm] = None, - ) -> bool: - """Verify signature. - - :param signature: SM2 signature to verify - :param data: Signed data - :param algorithm: Just to keep compatibility with abstract class - :raises SPSDKError: Invalid signature - """ - # Check if the signature is BER formatted - if len(signature) > 64 and signature[0] == 0x30: - signature = SM2Encoder().decode_signature(signature) - # Otherwise the signature is in raw format r || s - data_hash = bytes.fromhex(self.key._sm3_z(data)) - return self.key.verify(Sign=signature.hex(), data=data_hash) - - def export(self, encoding: SPSDKEncoding = SPSDKEncoding.DER) -> bytes: - """Convert key into bytes supported by NXP. - - :return: Byte representation of key - """ - if encoding != SPSDKEncoding.DER: - raise SPSDKNotImplementedError( - "Only DER enocding is supported for SM2 keys export" - ) - keys = SM2PublicKey(self.key.public_key) - return SM2Encoder().encode_public_key(keys) - - @property - def signature_size(self) -> int: - """Signature size.""" - return 64 - - @property - def public_numbers(self) -> str: - """Public numbers of key. - - :return: Public numbers - """ - return self.key.public_key - - @classmethod - def recreate(cls, data: bytes) -> Self: - """Recreate SM2 public key from data. - - :param data: public key data - :return: SPSDK public key. - """ - return cls(sm2.CryptSM2(private_key=None, public_key=data.hex())) - - @classmethod - def recreate_from_data(cls, data: bytes) -> Self: - """Recreate SM2 public key from data. - - :param data: PEM or DER encoded key. - :return: SM2 public key. - """ - key_data = sanitize_pem(data) - public_key = SM2Encoder().decode_public_key(data=key_data) - return cls(sm2.CryptSM2(private_key=None, public_key=public_key.public)) - - def __repr__(self) -> str: - return "SM2 Public Key" - - def __str__(self) -> str: - """Object description in string format.""" - ret = f"SM2 Public Key <{self.public_numbers}>" - return ret - -else: - # In case the OSCCA is not installed, do this to avoid import errors - PrivateKeySM2 = PrivateKey # type: ignore - PublicKeySM2 = PublicKey # type: ignore - - class ECDSASignature: """ECDSA Signature.""" @@ -1448,8 +1200,6 @@ def get_supported_keys_generators() -> KeyGeneratorInfo: "secp384r1": (PrivateKeyEcc.generate_key, {"curve_name": "secp384r1"}), "secp521r1": (PrivateKeyEcc.generate_key, {"curve_name": "secp521r1"}), } - if IS_OSCCA_SUPPORTED: - ret["sm2"] = (PrivateKeySM2.generate_key, {}) return ret diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/oscca.py b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/oscca.py deleted file mode 100644 index d315c043..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/oscca.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2022-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Support for OSCCA SM2/SM3.""" - - -from .. import SPSDK_DATA_FOLDER_COMMON -from ..utils.misc import Endianness - -try: - # this import is to find out whether OSCCA support is installed or not - # pylint: disable=unused-import - import gmssl - - IS_OSCCA_SUPPORTED = True -except ImportError: - IS_OSCCA_SUPPORTED = False - - -if IS_OSCCA_SUPPORTED: - import base64 - import os - from typing import Any, NamedTuple, Optional, Type, TypeVar - - from ..exceptions import SPSDKError - - OSCCA_ASN_DEFINITION_FILE = os.path.join( - SPSDK_DATA_FOLDER_COMMON, "crypto", "oscca.asn" - ) - SM2_OID = "1.2.156.10197.1.301" - - class SM2KeySet(NamedTuple): - """Bare-bone representation of a SM2 Key.""" - - private: str - public: Optional[str] - - class SM2PublicKey(NamedTuple): - """Bare-bone representation of a SM2 Public Key.""" - - public: str - - _T = TypeVar("_T") - - def singleton(class_: Type[_T]) -> Type[_T]: - """Decorator providing Singleton functionality for classes.""" - instances = {} - - def getinstance(*args: Any, **kwargs: Any) -> _T: - # args/kwargs should be part of cache key - if class_ not in instances: - instances[class_] = class_(*args, **kwargs) - return instances[class_] - - return getinstance # type: ignore # why are we even using Mypy?! - - @singleton - class SM2Encoder: - """ASN1 Encoder/Decoder for SM2 keys and signature.""" - - def __init__(self, asn_file: str = OSCCA_ASN_DEFINITION_FILE) -> None: - """Create ASN encoder/decoder based on provided ASN file.""" - try: - import asn1tools - except ImportError as import_error: - raise SPSDKError( - "asn1tools package is missing, " - "please install it with pip install 'spsdk[oscca]' in order to use OSCCA" - ) from import_error - - self.parser = asn1tools.compile_files(asn_file) - - def decode_private_key(self, data: bytes) -> SM2KeySet: - """Parse private SM2 key set from binary data.""" - result = self.parser.decode("Private", data) - key_set = self.parser.decode("KeySet", result["keyset"]) - return SM2KeySet( - private=key_set["prk"].hex(), public=key_set["puk"][0][1:].hex() - ) - - def decode_public_key(self, data: bytes) -> SM2PublicKey: - """Parse public SM2 key set from binary data.""" - result = self.parser.decode("Public", data) - return SM2PublicKey(public=result["puk"][0][1:].hex()) - - def encode_private_key(self, keys: SM2KeySet) -> bytes: - """Encode private SM2 key set from keyset.""" - assert isinstance(keys.public, str) - puk_array = bytearray(bytes.fromhex(keys.public)) - puk_array[0:0] = b"\x04" # 0x4 must be prepended - puk = (puk_array, 520) # tuple contains 520 - keyset = self.parser.encode( - "KeySet", - data={ - "number": 1, - "prk": bytes.fromhex(keys.private), - "puk": puk, - }, - ) - private_key = {"number": 0, "ids": [SM2_OID, SM2_OID], "keyset": keyset} - return self.parser.encode("Private", data=private_key) - - def encode_public_key(self, key: SM2PublicKey) -> bytes: - """Encode public SM2 key from SM2PublicKey.""" - puk_array = bytearray(bytes.fromhex(key.public)) - puk_array[0:0] = b"\x04" # 0x4 must be prepended - puk = (puk_array, 520) # tuple contains 520 - data = {"ids": [SM2_OID, SM2_OID], "puk": puk} - return self.parser.encode("Public", data=data) - - def decode_signature(self, data: bytes) -> bytes: - """Decode BER signature into r||s coordinates.""" - result = self.parser.decode("Signature", data) - r = int.to_bytes(result["r"], length=32, byteorder=Endianness.BIG.value) - s = int.to_bytes(result["s"], length=32, byteorder=Endianness.BIG.value) - return r + s - - def encode_signature(self, data: bytes) -> bytes: - """Encode raw r||s signature into BER format.""" - if len(data) != 64: - raise SPSDKError("SM2 signature must be 64B long.") - r = int.from_bytes(data[:32], byteorder=Endianness.BIG.value) - s = int.from_bytes(data[32:], byteorder=Endianness.BIG.value) - ber_signature = self.parser.encode("Signature", data={"r": r, "s": s}) - return ber_signature - - def sanitize_pem(data: bytes) -> bytes: - """Covert PEM data into DER.""" - if b"---" not in data: - return data - - capture_data = False - base64_data = b"" - for line in data.splitlines(keepends=False): - if capture_data: - base64_data += line - # PEM data may contain EC PARAMS, thus capture trigger should be the word KEY - if b"KEY" in line: - capture_data = not capture_data - # in the end the `capture_data` flag should be false singaling propper END * KEY - # and we should have some data - if capture_data is False and len(base64_data) > 0: - der_data = base64.b64decode(base64_data) - return der_data - raise SPSDKError("PEM data are corrupted") diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/signature_provider.py b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/signature_provider.py index eb9b5171..77fd17b8 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/crypto/signature_provider.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/crypto/signature_provider.py @@ -29,10 +29,8 @@ PrivateKey, PrivateKeyEcc, PrivateKeyRsa, - PrivateKeySM2, PublicKeyEcc, PublicKeyRsa, - PublicKeySM2, SPSDKKeyPassphraseMissing, prompt_for_passphrase, ) @@ -218,8 +216,6 @@ def _get_hash_algorithm( hash_size = 512 hash_alg_name = EnumHashAlgorithm.from_label(f"sha{hash_size}") - elif isinstance(self.private_key, PrivateKeySM2): - hash_alg_name = EnumHashAlgorithm.SM3 else: raise SPSDKError( f"Unsupported private key by signature provider: {str(self.private_key)}" @@ -241,10 +237,6 @@ def verify_public_key(self, public_key: bytes) -> bool: return self.private_key.verify_public_key(PublicKeyRsa.parse(public_key)) except SPSDKError: pass - try: - return self.private_key.verify_public_key(PublicKeySM2.parse(public_key)) - except SPSDKError: - pass raise SPSDKError("Unsupported public key") def info(self) -> str: diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/ele/__init__.py b/pynitrokey/trussed/bootloader/lpc55_upload/ele/__init__.py deleted file mode 100644 index 2dfaaee1..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/ele/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""This module contains support for EdgeLock Enclave Tool.""" diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_comm.py b/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_comm.py deleted file mode 100644 index 09914ec0..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_comm.py +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""EdgeLock Enclave Message handler.""" - -import logging -import re -from abc import abstractmethod -from types import TracebackType -from typing import List, Optional, Tuple, Type, Union - -from ..ele.ele_constants import ResponseStatus -from ..ele.ele_message import EleMessage -from ..exceptions import SPSDKError, SPSDKLengthError -from ..mboot.mcuboot import McuBoot -from ..uboot.uboot import Uboot -from ..utils.database import DatabaseManager, get_db, get_families -from ..utils.misc import value_to_bytes - -logger = logging.getLogger(__name__) - - -class EleMessageHandler: - """Base class for ELE message handling.""" - - def __init__( - self, device: Union[McuBoot, Uboot], family: str, revision: str = "latest" - ) -> None: - """Class object initialized. - - :param device: Communication interface. - :param family: Target family name. - :param revision: Target revision, default is use 'latest' revision. - """ - self.device = device - self.database = get_db(device=family, revision=revision) - self.family = family - self.revision = revision - self.comm_buff_addr = self.database.get_int( - DatabaseManager.COMM_BUFFER, "address" - ) - self.comm_buff_size = self.database.get_int(DatabaseManager.COMM_BUFFER, "size") - logger.info( - f"ELE communicator is using {self.comm_buff_size} B size buffer at " - f"{self.comm_buff_addr:08X} address in {family} target." - ) - - @staticmethod - def get_supported_families() -> List[str]: - """Get list of supported target families. - - :return: List of supported families. - """ - return get_families(DatabaseManager.ELE) - - @staticmethod - def get_ele_device(device: str) -> str: - """Get default ELE device from DB.""" - return get_db(device, "latest").get_str(DatabaseManager.ELE, "ele_device") - - @abstractmethod - def send_message(self, msg: EleMessage) -> None: - """Send message and receive response. - - :param msg: EdgeLock Enclave message - """ - - def __enter__(self) -> None: - """Enter function of ELE handler.""" - if not self.device.is_opened: - self.device.open() - - def __exit__( - self, - exception_type: Optional[Type[BaseException]] = None, - exception_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, - ) -> None: - """Close function of ELE handler.""" - if self.device.is_opened: - self.device.close() - - -class EleMessageHandlerMBoot(EleMessageHandler): - """EdgeLock Enclave Message Handler over MCUBoot. - - This class can send the ELE message into target over mBoot and decode the response. - """ - - def __init__(self, device: McuBoot, family: str, revision: str = "latest") -> None: - """Class object initialized. - - :param device: mBoot device. - :param family: Target family name. - :param revision: Target revision, default is use 'latest' revision. - """ - if not isinstance(device, McuBoot): - raise SPSDKError("Wrong instance of device, must be MCUBoot") - super().__init__(device, family, revision) - - def send_message(self, msg: EleMessage) -> None: - """Send message and receive response. - - :param msg: EdgeLock Enclave message - :raises SPSDKError: Invalid response status detected. - :raises SPSDKLengthError: Invalid read back length detected. - """ - if not isinstance(self.device, McuBoot): - raise SPSDKError("Wrong instance of device, must be MCUBoot") - msg.set_buffer_params(self.comm_buff_addr, self.comm_buff_size) - try: - # 1. Prepare command in target memory - self.device.write_memory(msg.command_address, msg.export()) - - # 1.1. Prepare command data in target memory if required - if msg.has_command_data: - self.device.write_memory(msg.command_data_address, msg.command_data) - - # 2. Execute ELE message on target - self.device.ele_message( - msg.command_address, - msg.command_words_count, - msg.response_address, - msg.response_words_count, - ) - if msg.response_words_count == 0: - return - # 3. Read back the response - response = self.device.read_memory( - msg.response_address, 4 * msg.response_words_count - ) - except SPSDKError as exc: - raise SPSDKError( - f"ELE Communication failed with mBoot: {str(exc)}" - ) from exc - - if not response or len(response) != 4 * msg.response_words_count: - raise SPSDKLengthError( - "ELE Message - Invalid response read-back operation." - ) - # 4. Decode the response - msg.decode_response(response) - - # 4.1 Check the response status - if msg.status != ResponseStatus.ELE_SUCCESS_IND: - raise SPSDKError(f"ELE Message failed. \n{msg.info()}") - - # 4.2 Read back the response data from target memory if required - if msg.has_response_data: - try: - response_data = self.device.read_memory( - msg.response_data_address, msg.response_data_size - ) - except SPSDKError as exc: - raise SPSDKError( - f"ELE Communication failed with mBoot: {str(exc)}" - ) from exc - - if not response_data or len(response_data) != msg.response_data_size: - raise SPSDKLengthError( - "ELE Message - Invalid response data read-back operation." - ) - - msg.decode_response_data(response_data) - - logger.info(f"Sent message information:\n{msg.info()}") - - -class EleMessageHandlerUBoot(EleMessageHandler): - """EdgeLock Enclave Message Handler over UBoot. - - This class can send the ELE message into target over UBoot and decode the response. - """ - - def __init__(self, device: Uboot, family: str, revision: str = "latest") -> None: - """Class object initialized. - - :param device: UBoot device. - :param family: Target family name. - :param revision: Target revision, default is use 'latest' revision. - """ - if not isinstance(device, Uboot): - raise SPSDKError("Wrong instance of device, must be UBoot") - super().__init__(device, family, revision) - - def extract_error_values(self, error_message: str) -> Tuple[int, int, int]: - """Extract error values from error_mesage. - - :param error_message: Error message containing ret and response - :return: abort_code, status and indication - """ - # Define regular expressions to extract values - ret_pattern = re.compile(r"ret (0x[0-9a-fA-F]+),") - response_pattern = re.compile(r"response (0x[0-9a-fA-F]+)") - - # Find matches in the error message - ret_match = ret_pattern.search(error_message) - response_match = response_pattern.search(error_message) - - if not ret_match or not response_match: - logger.error(f"Cannot decode error message from ELE!\n{error_message}") - abort_code = 0 - status = 0 - indication = 0 - else: - abort_code = int(ret_match.group(1), 16) - status_all = int(response_match.group(1), 16) - indication = status_all >> 8 - status = status_all & 0xFF - return abort_code, status, indication - - def send_message(self, msg: EleMessage) -> None: - """Send message and receive response. - - :param msg: EdgeLock Enclave message - :raises SPSDKError: Invalid response status detected. - :raises SPSDKLengthError: Invalid read back length detected. - """ - if not isinstance(self.device, Uboot): - raise SPSDKError("Wrong instance of device, must be UBoot") - msg.set_buffer_params(self.comm_buff_addr, self.comm_buff_size) - - try: - logger.debug( - f"ELE msg {hex(msg.buff_addr)} {hex(msg.buff_size)} {msg.export().hex()}" - ) - - # 0. Prepare command data in target memory if required - if msg.has_command_data: - self.device.write_memory(msg.command_data_address, msg.command_data) - - # 1. Execute ELE message on target - self.device.write( - f"ele_message {hex(msg.buff_addr)} {hex(msg.buff_size)} {msg.export().hex()}" - ) - output = self.device.read_output() - logger.debug(f"Raw ELE message output:\n{output}") - - if msg.response_words_count == 0: - return - - if "Error" in output: - msg.abort_code, msg.status, msg.indication = self.extract_error_values( - output - ) - else: - # 2. Read back the response - stripped_output = output.splitlines()[-1].replace("u-boot=> ", "") - logger.debug(f"Stripped output {stripped_output}") - response = value_to_bytes("0x" + stripped_output) - except (SPSDKError, IndexError) as exc: - raise SPSDKError( - f"ELE Communication failed with UBoot: {str(exc)}" - ) from exc - - if not "Error" in output: - if not response or len(response) != 4 * msg.response_words_count: - raise SPSDKLengthError( - "ELE Message - Invalid response read-back operation." - ) - # 3. Decode the response - msg.decode_response(response) - - # 3.1 Check the response status - if msg.status != ResponseStatus.ELE_SUCCESS_IND: - raise SPSDKError(f"ELE Message failed. \n{msg.info()}") - - # 3.2 Read back the response data from target memory if required - if msg.has_response_data: - try: - response_data = self.device.read_memory( - msg.response_data_address, msg.response_data_size - ) - self.device.read_output() - except SPSDKError as exc: - raise SPSDKError( - f"ELE Communication failed with mBoot: {str(exc)}" - ) from exc - - if not response_data or len(response_data) != msg.response_data_size: - raise SPSDKLengthError( - "ELE Message - Invalid response data read-back operation." - ) - - msg.decode_response_data(response_data) - - logger.info(f"Sent message information:\n{msg.info()}") diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_constants.py b/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_constants.py deleted file mode 100644 index 23427186..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_constants.py +++ /dev/null @@ -1,428 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""EdgeLock Enclave Message constants.""" - -from ..utils.spsdk_enum import SpsdkEnum, SpsdkSoftEnum - - -class MessageIDs(SpsdkSoftEnum): - """ELE Messages ID.""" - - PING_REQ = (0x01, "PING_REQ", "Ping request.") - ELE_FW_AUTH_REQ = (0x02, "ELE_FW_AUTH_REQ", "ELE firmware authenticate request.") - ELE_DUMP_DEBUG_BUFFER_REQ = (0x21, "ELE_DUMP_DEBUG_BUFFER_REQ", "Dump the ELE logs") - ELE_OEM_CNTN_AUTH_REQ = ( - 0x87, - "ELE_OEM_CNTN_AUTH_REQ", - "OEM Container authenticate", - ) - ELE_VERIFY_IMAGE_REQ = (0x88, "ELE_VERIFY_IMAGE_REQ", "Verify Image") - ELE_RELEASE_CONTAINER_REQ = ( - 0x89, - "ELE_RELEASE_CONTAINER_REQ", - "Release Container.", - ) - WRITE_SEC_FUSE_REQ = (0x91, "WRITE_SEC_FUSE_REQ", "Write secure fuse request.") - ELE_FWD_LIFECYCLE_UP_REQ = ( - 0x95, - "ELE_FWD_LIFECYCLE_UP_REQ", - "Forward Lifecycle update", - ) - READ_COMMON_FUSE = (0x97, "READ_COMMON_FUSE", "Read common fuse request.") - GET_FW_VERSION_REQ = (0x9D, "GET_FW_VERSION_REQ", "Get firmware version request.") - RETURN_LIFECYCLE_UPDATE_REQ = ( - 0xA0, - "RETURN_LIFECYCLE_UPDATE_REQ", - "Return lifecycle update request.", - ) - ELE_GET_EVENTS_REQ = (0xA2, "ELE_GET_EVENTS_REQ", "Get Events") - LOAD_KEY_BLOB_REQ = (0xA7, "LOAD_KEY_BLOB_REQ", "Load KeyBlob request.") - ELE_COMMIT_REQ = (0xA8, "ELE_COMMIT_REQ", "EdgeLock Enclave commit request.") - ELE_DERIVE_KEY_REQ = (0xA9, "ELE_DERIVE_KEY_REQ", "Derive key") - GENERATE_KEY_BLOB_REQ = (0xAF, "GENERATE_KEY_BLOB_REQ", "Generate KeyBlob request.") - GET_FW_STATUS_REQ = (0xC5, "GET_FW_STATUS_REQ", "Get ELE FW status request.") - ELE_ENABLE_APC_REQ = ( - 0xD2, - "ELE_ENABLE_APC_REQ", - "Enable APC (Application processor)", - ) - ELE_ENABLE_RTC_REQ = (0xD3, "ELE_ENABLE_RTC_REQ", "Enable RTC (Runtime processor)") - GET_INFO_REQ = (0xDA, "GET_INFO_REQ", "Get ELE Information request.") - ELE_RESET_APC_CTX_REQ = (0xD8, "ELE_RESET_APC_CTX_REQ", "Reset APC Context") - START_RNG_REQ = (0xA3, "START_RNG_REQ", "Start True Random Generator request.") - GET_TRNG_STATE_REQ = ( - 0xA3, - "GET_TRNG_STATE_REQ", - "Get True Random Generator state request.", - ) - RESET_REQ = (0xC7, "RESET_REQ", "System reset request.") - WRITE_FUSE = (0xD6, "WRITE_FUSE", "Write fuse") - WRITE_SHADOW_FUSE = (0xF2, "WRITE_SHADOW_FUSE", "Write shadow fuse") - READ_SHADOW_FUSE = (0xF3, "READ_SHADOW_FUSE", "Read shadow fuse request.") - - -class LifeCycle(SpsdkSoftEnum): - """ELE life cycles.""" - - LC_BLANK = (0x002, "BLANK", "Blank device") - LC_FAB = (0x004, "FAB", "Fab mode") - LC_NXP_PROV = (0x008, "NXP_PROV", "NXP Provisioned") - LC_OEM_OPEN = (0x010, "OEM_OPEN", "OEM Open") - LC_OEM_SWC = (0x020, "OEM_SWC", "OEM Secure World Closed") - LC_OEM_CLSD = (0x040, "OEM_CLSD", "OEM Closed") - LC_OEM_FR = (0x080, "OEM_FR", "Field Return OEM") - LC_NXP_FR = (0x100, "NXP_FR", "Field Return NXP") - LC_OEM_LCKD = (0x200, "OEM_LCKD", "OEM Locked") - LC_BRICKED = (0x400, "BRICKED", "BRICKED") - - -class LifeCycleToSwitch(SpsdkSoftEnum): - """ELE life cycles to switch request.""" - - OEM_CLOSED = (0x08, "OEM_CLOSED", "OEM Closed") - OEM_LOCKED = (0x80, "OEM_LOCKED", "OEM Locked") - - -class MessageUnitId(SpsdkSoftEnum): - """Message Unit ID.""" - - RTD_MU = (0x01, "RTD_MU", "Real Time Device message unit") - APD_MU = (0x02, "APD_MU", "Application Processor message unit") - - -class ResponseStatus(SpsdkEnum): - """ELE Message Response status.""" - - ELE_SUCCESS_IND = (0xD6, "Success", "The request was successful") - ELE_FAILURE_IND = (0x29, "Failure", "The request failed") - - -class ResponseIndication(SpsdkSoftEnum): - """ELE Message Response indication.""" - - ELE_ROM_PING_FAILURE_IND = (0x0A, "ELE_ROM_PING_FAILURE_IND", "ROM ping failure") - ELE_FW_PING_FAILURE_IND = (0x1A, "ELE_FW_PING_FAILURE_IND", "Firmware ping failure") - ELE_UNALIGNED_PAYLOAD_FAILURE_IND = ( - 0xA6, - "ELE_UNALIGNED_PAYLOAD_FAILURE_IND", - "Un-aligned payload failure", - ) - ELE_WRONG_SIZE_FAILURE_IND = ( - 0xA7, - "ELE_WRONG_SIZE_FAILURE_IND", - "Wrong size failure", - ) - ELE_ENCRYPTION_FAILURE_IND = ( - 0xA8, - "ELE_ENCRYPTION_FAILURE_IND", - "Encryption failure", - ) - ELE_DECRYPTION_FAILURE_IND = ( - 0xA9, - "ELE_DECRYPTION_FAILURE_IND", - "Decryption failure", - ) - ELE_OTP_PROGFAIL_FAILURE_IND = ( - 0xAA, - "ELE_OTP_PROGFAIL_FAILURE_IND", - "OTP program fail failure", - ) - ELE_OTP_LOCKED_FAILURE_IND = ( - 0xAB, - "ELE_OTP_LOCKED_FAILURE_IND", - "OTP locked failure", - ) - ELE_OTP_INVALID_IDX_FAILURE_IND = ( - 0xAD, - "ELE_OTP_INVALID_IDX_FAILURE_IND", - "OTP Invalid IDX failure", - ) - ELE_TIME_OUT_FAILURE_IND = (0xB0, "ELE_TIME_OUT_FAILURE_IND", "Timeout failure") - ELE_BAD_PAYLOAD_FAILURE_IND = ( - 0xB1, - "ELE_BAD_PAYLOAD_FAILURE_IND", - "Bad payload failure", - ) - ELE_WRONG_ADDRESS_FAILURE_IND = ( - 0xB4, - "ELE_WRONG_ADDRESS_FAILURE_IND", - "Wrong address failure", - ) - ELE_DMA_FAILURE_IND = (0xB5, "ELE_DMA_FAILURE_IND", "DMA failure") - ELE_DISABLED_FEATURE_FAILURE_IND = ( - 0xB6, - "ELE_DISABLED_FEATURE_FAILURE_IND", - "Disabled feature failure", - ) - ELE_MUST_ATTEST_FAILURE_IND = ( - 0xB7, - "ELE_MUST_ATTEST_FAILURE_IND", - "Must attest failure", - ) - ELE_RNG_NOT_STARTED_FAILURE_IND = ( - 0xB8, - "ELE_RNG_NOT_STARTED_FAILURE_IND", - "Random number generator not started failure", - ) - ELE_CRC_ERROR_IND = (0xB9, "ELE_CRC_ERROR_IND", "CRC error") - ELE_AUTH_SKIPPED_OR_FAILED_FAILURE_IND = ( - 0xBB, - "ELE_AUTH_SKIPPED_OR_FAILED_FAILURE_IND", - "Authentication skipped or failed failure", - ) - ELE_INCONSISTENT_PAR_FAILURE_IND = ( - 0xBC, - "ELE_INCONSISTENT_PAR_FAILURE_IND", - "Inconsistent parameter failure", - ) - ELE_RNG_INST_FAILURE_IND = ( - 0xBD, - "ELE_RNG_INST_FAILURE_IND", - "Random number generator instantiation failure", - ) - ELE_LOCKED_REG_FAILURE_IND = ( - 0xBE, - "ELE_LOCKED_REG_FAILURE_IND", - "Locked register failure", - ) - ELE_BAD_ID_FAILURE_IND = (0xBF, "ELE_BAD_ID_FAILURE_IND", "Bad ID failure") - ELE_INVALID_OPERATION_FAILURE_IND = ( - 0xC0, - "ELE_INVALID_OPERATION_FAILURE_IND", - "Invalid operation failure", - ) - ELE_NON_SECURE_STATE_FAILURE_IND = ( - 0xC1, - "ELE_NON_SECURE_STATE_FAILURE_IND", - "Non secure state failure", - ) - ELE_MSG_TRUNCATED_IND = (0xC2, "ELE_MSG_TRUNCATED_IND", "Message truncated failure") - ELE_BAD_IMAGE_NUM_FAILURE_IND = ( - 0xC3, - "ELE_BAD_IMAGE_NUM_FAILURE_IND", - "Bad image number failure", - ) - ELE_BAD_IMAGE_ADDR_FAILURE_IND = ( - 0xC4, - "ELE_BAD_IMAGE_ADDR_FAILURE_IND", - "Bad image address failure", - ) - ELE_BAD_IMAGE_PARAM_FAILURE_IND = ( - 0xC5, - "ELE_BAD_IMAGE_PARAM_FAILURE_IND", - "Bad image parameters failure", - ) - ELE_BAD_IMAGE_TYPE_FAILURE_IND = ( - 0xC6, - "ELE_BAD_IMAGE_TYPE_FAILURE_IND", - "Bad image type failure", - ) - ELE_APC_ALREADY_ENABLED_FAILURE_IND = ( - 0xCB, - "ELE_APC_ALREADY_ENABLED_FAILURE_IND", - "APC already enabled failure", - ) - ELE_RTC_ALREADY_ENABLED_FAILURE_IND = ( - 0xCC, - "ELE_RTC_ALREADY_ENABLED_FAILURE_IND", - "RTC already enabled failure", - ) - ELE_WRONG_BOOT_MODE_FAILURE_IND = ( - 0xCD, - "ELE_WRONG_BOOT_MODE_FAILURE_IND", - "Wrong boot mode failure", - ) - ELE_OLD_VERSION_FAILURE_IND = ( - 0xCE, - "ELE_OLD_VERSION_FAILURE_IND", - "Old version failure", - ) - ELE_CSTM_FAILURE_IND = (0xCF, "ELE_CSTM_FAILURE_IND", "CSTM failure") - ELE_CORRUPTED_SRK_FAILURE_IND = ( - 0xD0, - "ELE_CORRUPTED_SRK_FAILURE_IND", - "Corrupted SRK failure", - ) - ELE_OUT_OF_MEMORY_IND = (0xD1, "ELE_OUT_OF_MEMORY_IND", "Out of memory failure") - - ELE_MUST_SIGNED_FAILURE_IND = ( - 0xE0, - "ELE_MUST_SIGNED_FAILURE_IND", - "Must be signed failure", - ) - ELE_NO_AUTHENTICATION_FAILURE_IND = ( - 0xEE, - "ELE_NO_AUTHENTICATION_FAILURE_IND", - "No authentication failure", - ) - ELE_BAD_SRK_SET_FAILURE_IND = ( - 0xEF, - "ELE_BAD_SRK_SET_FAILURE_IND", - "Bad SRK set failure", - ) - ELE_BAD_SIGNATURE_FAILURE_IND = ( - 0xF0, - "ELE_BAD_SIGNATURE_FAILURE_IND", - "Bad signature failure", - ) - ELE_BAD_HASH_FAILURE_IND = (0xF1, "ELE_BAD_HASH_FAILURE_IND", "Bad hash failure") - ELE_INVALID_LIFECYCLE_IND = (0xF2, "ELE_INVALID_LIFECYCLE_IND", "Invalid lifecycle") - ELE_PERMISSION_DENIED_FAILURE_IND = ( - 0xF3, - "ELE_PERMISSION_DENIED_FAILURE_IND", - "Permission denied failure", - ) - ELE_INVALID_MESSAGE_FAILURE_IND = ( - 0xF4, - "ELE_INVALID_MESSAGE_FAILURE_IND", - "Invalid message failure", - ) - ELE_BAD_VALUE_FAILURE_IND = (0xF5, "ELE_BAD_VALUE_FAILURE_IND", "Bad value failure") - ELE_BAD_FUSE_ID_FAILURE_IND = ( - 0xF6, - "ELE_BAD_FUSE_ID_FAILURE_IND", - "Bad fuse ID failure", - ) - ELE_BAD_CONTAINER_FAILURE_IND = ( - 0xF7, - "ELE_BAD_CONTAINER_FAILURE_IND", - "Bad container failure", - ) - ELE_BAD_VERSION_FAILURE_IND = ( - 0xF8, - "ELE_BAD_VERSION_FAILURE_IND", - "Bad version failure", - ) - ELE_INVALID_KEY_FAILURE_IND = ( - 0xF9, - "ELE_INVALID_KEY_FAILURE_IND", - "The key in the container is invalid", - ) - ELE_BAD_KEY_HASH_FAILURE_IND = ( - 0xFA, - "ELE_BAD_KEY_HASH_FAILURE_IND", - "The key hash verification does not match OTP", - ) - ELE_NO_VALID_CONTAINER_FAILURE_IND = ( - 0xFB, - "ELE_NO_VALID_CONTAINER_FAILURE_IND", - "No valid container failure", - ) - ELE_BAD_CERTIFICATE_FAILURE_IND = ( - 0xFC, - "ELE_BAD_CERTIFICATE_FAILURE_IND", - "Bad certificate failure", - ) - ELE_BAD_UID_FAILURE_IND = (0xFD, "ELE_BAD_UID_FAILURE_IND", "Bad UID failure") - ELE_BAD_MONOTONIC_COUNTER_FAILURE_IND = ( - 0xFE, - "ELE_BAD_MONOTONIC_COUNTER_FAILURE_IND", - "Bad monotonic counter failure", - ) - ELE_ABORT_IND = (0xFF, "ELE_ABORT_IND", "Abort") - - -class EleFwStatus(SpsdkSoftEnum): - """ELE Firmware status.""" - - ELE_FW_STATUS_NOT_IN_PLACE = (0, "ELE_FW_STATUS_NOT_IN_PLACE", "Not in place") - ELE_FW_STATUS_IN_PLACE = ( - 1, - "ELE_FW_STATUS_IN_PLACE", - "Authenticated and operational", - ) - - -class EleInfo2Commit(SpsdkSoftEnum): - """ELE Information type to be committed.""" - - NXP_SRK_REVOCATION = ( - 0x1 << 0, - "NXP_SRK_REVOCATION", - "SRK revocation of the NXP container", - ) - NXP_FW_FUSE = (0x1 << 1, "NXP_FW_FUSE", "FW fuse version of the NXP container") - OEM_SRK_REVOCATION = ( - 0x1 << 4, - "OEM_SRK_REVOCATION", - "SRK revocation of the OEM container", - ) - OEM_FW_FUSE = (0x1 << 5, "OEM_FW_FUSE", "FW fuse version of the OEM container") - - -class KeyBlobEncryptionAlgorithm(SpsdkSoftEnum): - """ELE KeyBlob encryption algorithms.""" - - AES_CBC = (0x03, "AES_CBC", "KeyBlob encryption algorithm AES CBC") - AES_CTR = (0x04, "AES_CTR", "KeyBlob encryption algorithm AES CTR") - AES_XTS = (0x37, "AES_XTS", "KeyBlob encryption algorithm AES XTS") - SM4_CBC = (0x2B, "SM4_CBC", "KeyBlob encryption algorithm SM4 CBC") - - -class KeyBlobEncryptionIeeCtrModes(SpsdkSoftEnum): - """IEE Keyblob mode attributes.""" - - AesCTRWAddress = (0x02, "CTR_WITH_ADDRESS", " AES CTR w address binding mode") - AesCTRWOAddress = (0x03, "CTR_WITHOUT_ADDRESS", " AES CTR w/o address binding mode") - AesCTRkeystream = (0x04, "CTR_KEY_STREAM", "AES CTR keystream only") - - -class EleTrngState(SpsdkSoftEnum): - """ELE TRNG state.""" - - ELE_TRNG_NOT_READY = ( - 0x0, - "ELE_TRNG_NOT_READY", - "True random generator not started yet. Use 'start-trng' command", - ) - ELE_TRNG_PROGRAM = (0x1, "ELE_TRNG_PROGRAM", "TRNG is in program mode") - ELE_TRNG_GENERATING_ENTROPY = ( - 0x2, - "ELE_TRNG_GENERATING_ENTROPY", - "TRNG is still generating entropy", - ) - ELE_TRNG_READY = ( - 0x3, - "ELE_TRNG_READY", - "TRNG entropy is valid and ready to be read", - ) - ELE_TRNG_ERROR = ( - 0x4, - "ELE_TRNG_ERROR", - "TRNG encounter an error while generating entropy", - ) - - -class EleCsalState(SpsdkSoftEnum): - """ELE CSAL state.""" - - ELE_CSAL_NOT_READY = ( - 0x0, - "ELE_CSAL_NOT_READY", - "Crypto Lib random context initialization is not done yet", - ) - ELE_CSAL_ON_GOING = ( - 0x1, - "ELE_CSAL_ON_GOING", - "Crypto Lib random context initialization is on-going", - ) - ELE_CSAL_READY = ( - 0x2, - "ELE_CSAL_READY", - "Crypto Lib random context initialization succeed", - ) - ELE_CSAL_ERROR = ( - 0x3, - "ELE_CSAL_ERROR", - "Crypto Lib random context initialization failed", - ) - ELE_CSAL_PAUSE = ( - 0x4, - "ELE_CSAL_PAUSE", - "Crypto Lib random context initialization is in 'pause' mode", - ) diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_message.py b/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_message.py deleted file mode 100644 index 09cfd6aa..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/ele/ele_message.py +++ /dev/null @@ -1,1585 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""EdgeLock Enclave Message.""" - - -import logging -from struct import pack, unpack -from typing import Dict, List, Optional - -from crcmod.predefined import mkPredefinedCrcFun - -from ..ele.ele_constants import ( - EleCsalState, - EleFwStatus, - EleInfo2Commit, - EleTrngState, - KeyBlobEncryptionAlgorithm, - KeyBlobEncryptionIeeCtrModes, - LifeCycle, - LifeCycleToSwitch, - MessageIDs, - MessageUnitId, - ResponseIndication, - ResponseStatus, -) -from ..exceptions import SPSDKParsingError, SPSDKValueError -from ..image.ahab.signed_msg import SignedMessage -from ..utils.misc import Endianness, align, align_block -from ..utils.spsdk_enum import SpsdkEnum - -logger = logging.getLogger(__name__) - -LITTLE_ENDIAN = "<" -UINT8 = "B" -UINT16 = "H" -UINT32 = "L" -UINT64 = "Q" -RESERVED = 0 - - -class EleMessage: - """Base class for any EdgeLock Enclave Message. - - Message contains a header - tag, command id, size and version. - """ - - CMD = 0x00 - TAG = 0x17 - RSP_TAG = 0xE1 - VERSION = 0x06 - HEADER_FORMAT = LITTLE_ENDIAN + UINT8 + UINT8 + UINT8 + UINT8 - COMMAND_HEADER_WORDS_COUNT = 1 - COMMAND_PAYLOAD_WORDS_COUNT = 0 - RESPONSE_HEADER_WORDS_COUNT = 2 - RESPONSE_PAYLOAD_WORDS_COUNT = 0 - ELE_MSG_ALIGN = 8 - MAX_RESPONSE_DATA_SIZE = 0 - MAX_COMMAND_DATA_SIZE = 0 - - def __init__(self) -> None: - """Class object initialized.""" - self.abort_code = 0 - self.indication = 0 - self.status = 0 - self.buff_addr = 0 - self.buff_size = 0 - self.command = self.CMD - self._response_data_size = self.MAX_RESPONSE_DATA_SIZE - - @property - def command_address(self) -> int: - """Command address in target memory space.""" - return align(self.buff_addr, self.ELE_MSG_ALIGN) - - @property - def command_words_count(self) -> int: - """Command Words count.""" - return self.COMMAND_HEADER_WORDS_COUNT + self.COMMAND_PAYLOAD_WORDS_COUNT - - @property - def has_command_data(self) -> bool: - """Check if command has additional data.""" - return bool(self.command_data_size > 0) - - @property - def command_data_address(self) -> int: - """Command data address in target memory space.""" - return align( - self.command_address + self.command_words_count * 4, self.ELE_MSG_ALIGN - ) - - @property - def command_data_size(self) -> int: - """Command data address in target memory space.""" - return align( - len(self.command_data) or self.MAX_COMMAND_DATA_SIZE, self.ELE_MSG_ALIGN - ) - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - return b"" - - @property - def response_address(self) -> int: - """Response address in target memory space.""" - if self.has_command_data: - address = self.command_data_address + self.command_data_size - else: - address = self.buff_addr + self.command_words_count * 4 - return align(address, self.ELE_MSG_ALIGN) - - @property - def response_words_count(self) -> int: - """Response Words count.""" - return self.RESPONSE_HEADER_WORDS_COUNT + self.RESPONSE_PAYLOAD_WORDS_COUNT - - @property - def has_response_data(self) -> bool: - """Check if response has additional data.""" - return bool(self.response_data_size > 0) - - @property - def response_data_address(self) -> int: - """Response data address in target memory space.""" - return align( - self.response_address + self.response_words_count * 4, self.ELE_MSG_ALIGN - ) - - @property - def response_data_size(self) -> int: - """Response data address in target memory space.""" - return align(self._response_data_size, self.ELE_MSG_ALIGN) - - @property - def free_space_address(self) -> int: - """First free address after ele message in target memory space.""" - return align( - self.response_data_address + self._response_data_size, self.ELE_MSG_ALIGN - ) - - @property - def free_space_size(self) -> int: - """Free space size after ele message in target memory space.""" - return align( - self.buff_size - (self.free_space_address - self.buff_addr), - self.ELE_MSG_ALIGN, - ) - - @property - def status_string(self) -> str: - """Get status in readable string format.""" - if self.status not in ResponseStatus: - return "Invalid status!" - if self.status == ResponseStatus.ELE_SUCCESS_IND: - return "Succeeded" - indication = ( - ResponseIndication.get_label(self.indication) - if ResponseIndication.contains(self.indication) - else f"Invalid indication code: {self.indication:02X}" - ) - return f"Failed: {indication}" - - def set_buffer_params(self, buff_addr: int, buff_size: int) -> None: - """Set the communication buffer parameters to allow command update addresses inside command payload. - - :param buff_addr: Real address of communication buffer in target memory space - :param buff_size: Size of communication buffer in target memory space - """ - self.buff_addr = buff_addr - self.buff_size = buff_size - - self.validate_buffer_params() - - def validate_buffer_params(self) -> None: - """Validate communication buffer parameters. - - raises SPSDKValueError: Invalid buffer parameters. - """ - if self.has_response_data: - needed_space = self.response_data_address + self.response_data_size - else: - needed_space = self.response_address + self.response_words_count * 4 - - if self.buff_size < needed_space - self.buff_addr: - raise SPSDKValueError( - "ELE Message: Communication buffer is to small to fit message. " - f"({needed_space-self.buff_addr} > {self.buff_size})" - ) - - def validate(self) -> None: - """Validate message.""" - - def header_export( - self, - ) -> bytes: - """Exports message header to bytes. - - :return: Bytes representation of message header. - """ - return pack( - self.HEADER_FORMAT, - self.VERSION, - self.command_words_count, - self.command, - self.TAG, - ) - - def export( - self, - ) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - return self.header_export() - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - # Decode and validate header - (version, size, command, tag) = unpack(self.HEADER_FORMAT, response[:4]) - if tag != self.RSP_TAG: - raise SPSDKParsingError(f"Message TAG in response is invalid: {hex(tag)}") - if command != self.command: - raise SPSDKParsingError( - f"Message COMMAND in response is invalid: {hex(command)}" - ) - if size not in [self.response_words_count, self.RESPONSE_HEADER_WORDS_COUNT]: - raise SPSDKParsingError(f"Message SIZE in response is invalid: {hex(size)}") - if version != self.VERSION: - raise SPSDKParsingError( - f"Message VERSION in response is invalid: {hex(version)}" - ) - - # Decode status word - ( - self.status, - self.indication, - self.abort_code, - ) = unpack(LITTLE_ENDIAN + UINT8 + UINT8 + UINT16, response[4:8]) - - def decode_response_data(self, response_data: bytes) -> None: - """Decode response data from target. - - :note: The response data are specific per command. - :param response_data: Data of response. - """ - - def __eq__(self, other: object) -> bool: - if isinstance(other, EleMessage): - if ( - self.TAG == other.TAG - and self.command == other.command - and self.VERSION == other.VERSION - and self.command_words_count == other.command_words_count - ): - return True - - return False - - @staticmethod - def get_msg_crc(payload: bytes) -> bytes: - """Compute message CRC. - - :param payload: The input data to compute CRC on them. Must be 4 bytes aligned. - :return: 4 bytes of CRC in little endian format. - """ - assert len(payload) % 4 == 0 - res = 0 - for i in range(0, len(payload), 4): - res ^= int.from_bytes(payload[i : i + 4], Endianness.LITTLE.value) - return res.to_bytes(4, Endianness.LITTLE.value) - - def response_status(self) -> str: - """Print the response status information. - - :return: String with response status. - """ - ret = f"Response status: {ResponseStatus.get_label(self.status)}\n" - if self.status == ResponseStatus.ELE_FAILURE_IND: - ret += ( - f" Response indication: {ResponseIndication.get_label(self.indication)}" - f" - ({hex(self.indication)})\n" - ) - ret += f" Response abort code: {hex(self.abort_code)}\n" - return ret - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - ret = f"Command: {MessageIDs.get_label(self.command)} - ({hex(self.command)})\n" - ret += f"Command words: {self.command_words_count}\n" - ret += f"Command data: {self.has_command_data}\n" - ret += f"Response words: {self.response_words_count}\n" - ret += f"Response data: {self.has_response_data}\n" - # if self.status in ResponseStatus: - ret += self.response_status() - - return ret - - -class EleMessagePing(EleMessage): - """ELE Message Ping.""" - - CMD = MessageIDs.PING_REQ.tag - - -class EleMessageDumpDebugBuffer(EleMessage): - """ELE Message Dump Debug buffer.""" - - CMD = MessageIDs.ELE_DUMP_DEBUG_BUFFER_REQ.tag - RESPONSE_PAYLOAD_WORDS_COUNT = 21 - - def __init__(self) -> None: - """Class object initialized.""" - super().__init__() - self.debug_words: List[int] = [0] * 20 - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - *self.debug_words, crc = unpack(LITTLE_ENDIAN + "20L4s", response[8:92]) - crc_computed = self.get_msg_crc(response[0:88]) - if crc != crc_computed: - raise SPSDKParsingError("Invalid message CRC for dump debug buffer") - - def response_info(self) -> str: - """Print Dumped data of debug buffer.""" - ret = "" - for i, dump_data in enumerate(self.debug_words): - ret += f"Dump debug word[{i}]: {dump_data:08X}\n" - - return ret - - -class EleMessageReset(EleMessage): - """ELE Message Reset.""" - - CMD = MessageIDs.RESET_REQ.tag - RESPONSE_HEADER_WORDS_COUNT = 0 - - -class EleMessageEleFwAuthenticate(EleMessage): - """Ele firmware authenticate request.""" - - CMD = MessageIDs.ELE_FW_AUTH_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 3 - - def __init__(self, ele_fw_address: int) -> None: - """Constructor. - - Be aware to have ELE FW in accessible memory for ROM, and - do not use the RAM memory used to communicate with ELE. - - :param ele_fw_address: Address in target memory with ele firmware. - """ - super().__init__() - self.ele_fw_address = ele_fw_address - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack( - LITTLE_ENDIAN + UINT32 + UINT32 + UINT32, - self.ele_fw_address, - 0, - self.ele_fw_address, - ) - return ret - - -class EleMessageOemContainerAuthenticate(EleMessage): - """OEM container authenticate request.""" - - CMD = MessageIDs.ELE_OEM_CNTN_AUTH_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 2 - - def __init__(self, oem_cntn_addr: int) -> None: - """Constructor. - - Be aware to have OEM Container in accessible memory for ROM. - - :param oem_cntn_addr: Address in target memory with oem container. - """ - super().__init__() - self.oem_cntn_addr = oem_cntn_addr - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack(LITTLE_ENDIAN + UINT32 + UINT32, 0, self.oem_cntn_addr) - return ret - - -class EleMessageVerifyImage(EleMessage): - """Verify image request.""" - - CMD = MessageIDs.ELE_VERIFY_IMAGE_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 1 - RESPONSE_PAYLOAD_WORDS_COUNT = 2 - - def __init__(self, image_mask: int = 0x0000_0001) -> None: - """Constructor. - - The Verify Image message is sent to the ELE after a container has been - loaded into memory and processed with an Authenticate Container message. - This commands the ELE to check the hash on one or more images. - - :param image_mask: Used to indicate which images are to be checked. There must be at least - one image. Each bit corresponds to a particular image index in the header, for example, - bit 0 is for image 0, and bit 1 is for image 1, and so on. - """ - super().__init__() - self.image_mask = image_mask - self.valid_image_mask = 0 - self.invalid_image_mask = 0xFFFF_FFFF - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack(LITTLE_ENDIAN + UINT32, self.image_mask) - return ret - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.valid_image_mask, self.invalid_image_mask = unpack( - LITTLE_ENDIAN + "LL", response[8:16] - ) - checked_mask = self.valid_image_mask | self.invalid_image_mask - if self.image_mask != checked_mask: - logger.error( - "The invalid&valid mask doesn't cover requested mask to check! " - f"valid: 0x{self.valid_image_mask:08X} | invalid: 0x{self.invalid_image_mask:08X}" - f" != requested: 0x{self.image_mask:08X}" - ) - - def response_info(self) -> str: - """Print Dumped data of debug buffer.""" - ret = f"Valid image mask : 0x{self.valid_image_mask:08X}\n" - ret += f"Invalid image mask : 0x{self.invalid_image_mask:08X}" - return ret - - -class EleMessageReleaseContainer(EleMessage): - """ELE Message Release container.""" - - CMD = MessageIDs.ELE_RELEASE_CONTAINER_REQ.tag - - -class EleMessageForwardLifeCycleUpdate(EleMessage): - """Forward Life cycle update request.""" - - CMD = MessageIDs.ELE_FWD_LIFECYCLE_UP_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 1 - - def __init__(self, lifecycle_update: LifeCycleToSwitch) -> None: - """Constructor. - - Be aware that this is non-revertible operation. - - :param lifecycle_update: New life cycle value. - """ - super().__init__() - self.lifecycle_update = lifecycle_update - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack( - LITTLE_ENDIAN + UINT16 + UINT8 + UINT8, self.lifecycle_update.tag, 0, 0 - ) - return ret - - -class EleMessageGetEvents(EleMessage): - """Get events request. - - \b - Event layout: - ------------------------- - - TAG - CMD - IND - STS - - ------------------------- - \b - """ - - CMD = MessageIDs.ELE_GET_EVENTS_REQ.tag - RESPONSE_PAYLOAD_WORDS_COUNT = 10 - - MAX_EVENT_CNT = 8 - - def __init__(self) -> None: - """Constructor. - - This message is used to retrieve any singular event that has occurred since the FW has - started. A singular event occurs when the second word of a response to any request is - different from ELE_SUCCESS_IND. That includes commands with failure response as well as - commands with successful response containing an indication (i.e. warning response). - The events are stored by the ELE in a fixed sized buffer. When the capacity of the buffer - is exceeded, new occurring events are lost. - The event buffer is systematically returned in full to the requester independently of - the actual numbers of events stored. - """ - super().__init__() - self.event_cnt = 0 - self.events: List[int] = [0] * self.MAX_EVENT_CNT - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.event_cnt, max_events, *self.events, crc = unpack( - LITTLE_ENDIAN + UINT16 + UINT16 + "8L4s", response[8:48] - ) - if max_events != self.MAX_EVENT_CNT: - logger.error( - f"Invalid maximal events count: {max_events}!={self.MAX_EVENT_CNT}" - ) - - crc_computed = self.get_msg_crc(response[0:44]) - if crc != crc_computed: - logger.error("Invalid message CRC for get events message") - - @staticmethod - def get_ipc_id(event: int) -> str: - """Get IPC ID in string from event.""" - ipc_id = (event >> 24) & 0xFF - return MessageUnitId.get_description(ipc_id, f"Unknown MU: ({ipc_id})") or "" - - @staticmethod - def get_cmd(event: int) -> str: - """Get Command in string from event.""" - cmd = (event >> 16) & 0xFF - return MessageIDs.get_description(cmd, f"Unknown Command: (0x{cmd:02})") or "" - - @staticmethod - def get_ind(event: int) -> str: - """Get Indication in string from event.""" - ind = (event >> 8) & 0xFF - return ( - ResponseIndication.get_description(ind, f"Unknown Indication: (0x{ind:02})") - or "" - ) - - @staticmethod - def get_sts(event: int) -> str: - """Get Status in string from event.""" - sts = event & 0xFF - return ( - ResponseStatus.get_description(sts, f"Unknown Status: (0x{sts:02})") or "" - ) - - def response_info(self) -> str: - """Print events info.""" - ret = f"Event count: {self.event_cnt}" - for i, event in enumerate( - self.events[: min(self.event_cnt, self.MAX_EVENT_CNT)] - ): - ret += f"\nEvent[{i}]: 0x{event:08X}" - ret += f"\n IPC ID: {self.get_ipc_id(event)}" - ret += f"\n Command: {self.get_cmd(event)}" - ret += f"\n Indication: {self.get_ind(event)}" - ret += f"\n Status: {self.get_sts(event)}" - if self.event_cnt > self.MAX_EVENT_CNT: - ret += "\nEvent count is bigger than maximal supported, " - ret += f"only first {self.MAX_EVENT_CNT} events are listed." - return ret - - -class EleMessageStartTrng(EleMessage): - """ELE Message Start True Random Generator.""" - - CMD = MessageIDs.START_RNG_REQ.tag - - -class EleMessageGetTrngState(EleMessage): - """ELE Message Get True Random Generator State.""" - - CMD = MessageIDs.GET_TRNG_STATE_REQ.tag - RESPONSE_PAYLOAD_WORDS_COUNT = 1 - - def __init__(self) -> None: - """Class object initialized.""" - super().__init__() - self.ele_trng_state = EleTrngState.ELE_TRNG_PROGRAM.tag - self.ele_csal_state = EleCsalState.ELE_CSAL_NOT_READY.tag - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.ele_trng_state, self.ele_csal_state, _ = unpack( - LITTLE_ENDIAN + UINT8 + UINT8 + "2s", response[8:12] - ) - - def response_info(self) -> str: - """Print specific information of ELE. - - :return: Information about the TRNG. - """ - return ( - f"EdgeLock Enclave TRNG state: {EleTrngState.get_description(self.ele_trng_state)}" - + f"\nEdgeLock Enclave CSAL state: {EleCsalState.get_description(self.ele_csal_state)}" - ) - - -class EleMessageCommit(EleMessage): - """ELE Message Get FW status.""" - - CMD = MessageIDs.ELE_COMMIT_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 1 - RESPONSE_PAYLOAD_WORDS_COUNT = 1 - - def __init__(self, info_to_commit: List[EleInfo2Commit]) -> None: - """Class object initialized.""" - super().__init__() - self.info_to_commit = info_to_commit - - @property - def info2commit_mask(self) -> int: - """Get info to commit mask used in command.""" - ret = 0 - for rule in self.info_to_commit: - ret |= rule.tag - return ret - - def mask_to_info2commit(self, mask: int) -> List[EleInfo2Commit]: - """Get list of info to commit from mask.""" - ret = [] - for bit in range(32): - bit_mask = 1 << bit - if mask and bit_mask: - ret.append(EleInfo2Commit.from_tag(bit)) - return ret - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack(LITTLE_ENDIAN + UINT32, self.info2commit_mask) - return ret - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - mask = int.from_bytes(response[8:12], Endianness.LITTLE.value) - if mask != self.info2commit_mask: - logger.error( - f"Only those information has been committed: {[x.label for x in self.mask_to_info2commit(mask)]}," - f" from those:{[x.label for x in self.info_to_commit]}" - ) - - -class EleMessageGetFwStatus(EleMessage): - """ELE Message Get FW status.""" - - CMD = MessageIDs.GET_FW_STATUS_REQ.tag - RESPONSE_PAYLOAD_WORDS_COUNT = 1 - - def __init__(self) -> None: - """Class object initialized.""" - super().__init__() - self.ele_fw_status = EleFwStatus.ELE_FW_STATUS_NOT_IN_PLACE.tag - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.ele_fw_status, _ = unpack(LITTLE_ENDIAN + UINT8 + "3s", response[8:12]) - - def response_info(self) -> str: - """Print specific information of ELE. - - :return: Information about the ELE. - """ - return f"EdgeLock Enclave firmware state: {EleFwStatus.get_label(self.ele_fw_status)}" - - -class EleMessageGetFwVersion(EleMessage): - """ELE Message Get FW version.""" - - CMD = MessageIDs.GET_FW_VERSION_REQ.tag - RESPONSE_PAYLOAD_WORDS_COUNT = 2 - - def __init__(self) -> None: - """Class object initialized.""" - super().__init__() - self.ele_fw_version_raw = 0 - self.ele_fw_version_sha1 = 0 - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.ele_fw_version_raw = int.from_bytes( - response[8:12], Endianness.LITTLE.value - ) - self.ele_fw_version_sha1 = int.from_bytes( - response[12:16], Endianness.LITTLE.value - ) - - def response_info(self) -> str: - """Print specific information of ELE. - - :return: Information about the ELE. - """ - ret = ( - f"EdgeLock Enclave firmware version: {self.ele_fw_version_raw:08X}\n" - f"Readable form: {(self.ele_fw_version_raw>>16) & 0xff}." - f"{(self.ele_fw_version_raw>>4) & 0xfff}.{self.ele_fw_version_raw & 0xf}\n" - f"Commit SHA1 (First 4 bytes): {self.ele_fw_version_sha1:08X}" - ) - if self.ele_fw_version_raw & 1 << 31: - ret += "\nDirty build" - return ret - - -class EleMessageReadCommonFuse(EleMessage): - """ELE Message Read common fuse.""" - - CMD = MessageIDs.READ_COMMON_FUSE.tag - COMMAND_PAYLOAD_WORDS_COUNT = 1 - RESPONSE_PAYLOAD_WORDS_COUNT = 1 - - def __init__(self, index: int) -> None: - """Constructor. - - Read common fuse. - - :param index: Fuse ID. - """ - super().__init__() - self.index = index - self.fuse_value = 0 - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack(LITTLE_ENDIAN + UINT16 + UINT16, self.index, 0) - return ret - - def decode_response(self, response: bytes) -> None: - """Decode response from target. - - :param response: Data of response. - :raises SPSDKParsingError: Response parse detect some error. - """ - super().decode_response(response) - self.fuse_value = int.from_bytes(response[8:12], Endianness.LITTLE.value) - - def response_info(self) -> str: - """Print fuse value. - - :return: Read fuse value. - """ - return f"Fuse ID_{self.index}: 0x{self.fuse_value:08X}\n" - - -class EleMessageReadShadowFuse(EleMessageReadCommonFuse): - """ELE Message Read shadow fuse.""" - - CMD = MessageIDs.READ_SHADOW_FUSE.tag - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - ret += pack(LITTLE_ENDIAN + UINT32, self.index) - return ret - - -class EleMessageGetInfo(EleMessage): - """ELE Message Get Info.""" - - CMD = MessageIDs.GET_INFO_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 3 - MAX_RESPONSE_DATA_SIZE = 256 - - def __init__(self) -> None: - """Class object initialized.""" - super().__init__() - self.info_length = 0 - self.info_version = 0 - self.info_cmd = 0 - self.info_soc_rev = 0 - self.info_soc_id = 0 - self.info_life_cycle = 0 - self.info_sssm_state = 0 - self.info_uuid = bytes() - self.info_sha256_rom_patch = bytes() - self.info_sha256_fw = bytes() - self.info_oem_srkh = bytes() - self.info_imem_state = 0 - self.info_csal_state = 0 - self.info_trng_state = 0 - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - payload = pack( - LITTLE_ENDIAN + UINT32 + UINT32 + UINT16 + UINT16, - 0, - self.response_data_address, - self.response_data_size, - 0, - ) - return self.header_export() + payload - - def decode_response_data(self, response_data: bytes) -> None: - """Decode response data from target. - - :note: The response data are specific per command. - :param response_data: Data of response. - """ - (self.info_cmd, self.info_version, self.info_length) = unpack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT16, response_data[:4] - ) - - (self.info_soc_id, self.info_soc_rev) = unpack( - LITTLE_ENDIAN + UINT16 + UINT16, response_data[4:8] - ) - (self.info_life_cycle, self.info_sssm_state, _) = unpack( - LITTLE_ENDIAN + UINT16 + UINT8 + UINT8, response_data[8:12] - ) - self.info_uuid = response_data[12:28] - self.info_sha256_rom_patch = response_data[28:60] - self.info_sha256_fw = response_data[60:92] - if self.info_version == 0x02: - self.info_oem_srkh = response_data[92:156] - self.info_oem_srkh = response_data[92:156] - ( - self.info_trng_state, - self.info_csal_state, - self.info_imem_state, - _, - ) = unpack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT8 + UINT8, response_data[156:160] - ) - - def response_info(self) -> str: - """Print specific information of ELE. - - :return: Information about the ELE. - """ - ret = f"Command: {hex(self.info_cmd)}\n" - ret += f"Version: {self.info_version}\n" - ret += f"Length: {self.info_length}\n" - ret += f"SoC ID: {self.info_soc_id:04X}\n" - ret += f"SoC version: {self.info_soc_rev:04X}\n" - ret += f"Life Cycle: {LifeCycle.get_label(self.info_life_cycle)} - 0x{self.info_life_cycle:04X}\n" - ret += f"SSSM state: {self.info_sssm_state}\n" - ret += f"UUID: {self.info_uuid.hex()}\n" - ret += f"SHA256 ROM PATCH: {self.info_sha256_rom_patch.hex()}\n" - ret += f"SHA256 FW: {self.info_sha256_fw.hex()}\n" - if self.info_version == 0x02: - ret += "Advanced information:\n" - ret += f" OEM SRKH: {self.info_oem_srkh.hex()}\n" - ret += f" IMEM state: {self.info_imem_state}\n" - ret += ( - f" CSAL state: " - f"{EleCsalState.get_description(self.info_csal_state, str(self.info_csal_state))}\n" - ) - ret += ( - f" TRNG state: " - f"{EleTrngState.get_description(self.info_trng_state, str(self.info_trng_state))}\n" - ) - - return ret - - -class EleMessageDeriveKey(EleMessage): - """ELE Message Derive Key.""" - - CMD = MessageIDs.ELE_DERIVE_KEY_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 6 - MAX_RESPONSE_DATA_SIZE = 32 - _MAX_COMMAND_DATA_SIZE = 65536 - SUPPORTED_KEY_SIZES = [16, 32] - - def __init__(self, key_size: int, context: Optional[bytes]) -> None: - """Class object initialized. - - :param key_size: Output key size [16,32] is valid - :param context: User's context to be used for key diversification - """ - if key_size not in self.SUPPORTED_KEY_SIZES: - raise SPSDKValueError( - f"Output Key size ({key_size}) must be in {self.SUPPORTED_KEY_SIZES}" - ) - if context and len(context) > self._MAX_COMMAND_DATA_SIZE: - raise SPSDKValueError( - f"User context length ({len(context)}) <= {self._MAX_COMMAND_DATA_SIZE}" - ) - super().__init__() - self.key_size = key_size - self._response_data_size = key_size - self.context = context - self.derived_key = b"" - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - payload = pack( - LITTLE_ENDIAN + UINT32 + UINT32 + UINT32 + UINT32 + UINT16 + UINT16, - 0, - self.response_data_address, - 0, - self.command_data_address if self.context else 0, - self.key_size, - self.command_data_size, - ) - header = self.header_export() - return header + payload + self.get_msg_crc(header + payload) - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - return self.context if self.context else b"" - - def decode_response_data(self, response_data: bytes) -> None: - """Decode response data from target. - - :note: The response data are specific per command. - :param response_data: Data of response. - """ - self.derived_key = response_data[: self.key_size] - - def get_key(self) -> bytes: - """Get derived key.""" - return self.derived_key - - -class EleMessageSigned(EleMessage): - """ELE Message Signed.""" - - COMMAND_PAYLOAD_WORDS_COUNT = 2 - - def __init__(self, signed_msg: bytes) -> None: - """Class object initialized. - - :param signed_msg: Signed message container. - """ - super().__init__() - self.signed_msg_binary = signed_msg - # Get the command inside the signed message - self.signed_msg = SignedMessage.parse(signed_msg) - self.signed_msg.update_fields() - assert self.signed_msg.message - self.command = self.signed_msg.message.cmd - self._command_data_size = len(self.signed_msg_binary) - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - payload = pack( - LITTLE_ENDIAN + UINT32 + UINT32, - 0, - self.command_data_address, - ) - return self.header_export() + payload - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - return self.signed_msg_binary - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - ret = super().info() - ret += "\n" + self.signed_msg.image_info().draw() - - return ret - - -class EleMessageGenerateKeyBlob(EleMessage): - """ELE Message Generate KeyBlob.""" - - KEYBLOB_NAME = "Unknown" - # List of supported algorithms and theirs key sizes - SUPPORTED_ALGORITHMS: Dict[SpsdkEnum, List[int]] = {} - - KEYBLOB_TAG = 0x81 - KEYBLOB_VERSION = 0x00 - CMD = MessageIDs.GENERATE_KEY_BLOB_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 7 - MAX_RESPONSE_DATA_SIZE = 512 - - def __init__( - self, key_identifier: int, algorithm: KeyBlobEncryptionAlgorithm, key: bytes - ) -> None: - """Constructor of Generate Key Blob class. - - :param key_identifier: ID of key - :param algorithm: Select supported algorithm - :param key: Key to be wrapped - """ - super().__init__() - self.key_id = key_identifier - self.algorithm = algorithm - - self.key = key - self.key_blob = bytes() - self.validate() - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - payload = pack( - LITTLE_ENDIAN - + UINT32 - + UINT32 - + UINT32 - + UINT32 - + UINT32 - + UINT16 - + UINT16, - self.key_id, - 0, - self.command_data_address, - 0, - self.response_data_address, - self.MAX_RESPONSE_DATA_SIZE, - 0, - ) - payload = self.header_export() + payload - return payload + EleMessage.get_msg_crc(payload) - - def validate(self) -> None: - """Validate generate keyblob message data. - - :raises SPSDKValueError: Invalid used key size or encryption algorithm - """ - if self.algorithm not in self.SUPPORTED_ALGORITHMS: - raise SPSDKValueError( - f"{self.algorithm} is not supported by {self.KEYBLOB_NAME} keyblob in ELE." - ) - - if len(self.key) * 8 not in self.SUPPORTED_ALGORITHMS[self.algorithm]: - raise SPSDKValueError( - f"Unsupported size of input key by {self.KEYBLOB_NAME} keyblob" - f" for {self.algorithm.label} algorithm." - f"The list of supported keys in bit count: {self.SUPPORTED_ALGORITHMS[self.algorithm]}" - ) - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - ret = super().info() - ret += "\n" - ret += f"KeyBlob type: {self.KEYBLOB_NAME}\n" - ret += f"Key ID: {self.key_id}\n" - ret += f"Algorithm: {self.algorithm.label}\n" - ret += f"Key size: {len(self.key)*8} bits\n" - return ret - - @classmethod - def get_supported_algorithms(cls) -> List[str]: - """Get the list of supported algorithms. - - :return: List of supported algorithm names. - """ - return list(x.label for x in cls.SUPPORTED_ALGORITHMS) - - @classmethod - def get_supported_key_sizes(cls) -> str: - """Get table with supported key sizes per algorithm. - - :return: Table with supported key size in text. - """ - ret = "" - for key, value in cls.SUPPORTED_ALGORITHMS.items(): - ret += key.label + ": " + str(value) + ",\n" - return ret - - def decode_response_data(self, response_data: bytes) -> None: - """Decode response data from target. - - :note: The response data are specific per command. - :param response_data: Data of response. - :raises SPSDKParsingError: Invalid response detected. - """ - ver, length, tag = unpack( - LITTLE_ENDIAN + UINT8 + UINT16 + UINT8, response_data[:4] - ) - if tag != self.KEYBLOB_TAG: - raise SPSDKParsingError("Invalid TAG in generated KeyBlob") - if ver != self.KEYBLOB_VERSION: - raise SPSDKParsingError("Invalid Version in generated KeyBlob") - if length > self.MAX_RESPONSE_DATA_SIZE: - raise SPSDKParsingError("Invalid Length in generated KeyBlob") - - self.key_blob = response_data[:length] - - -class EleMessageGenerateKeyBlobDek(EleMessageGenerateKeyBlob): - """ELE Message Generate DEK KeyBlob.""" - - KEYBLOB_NAME = "DEK" - # List of supported algorithms and theirs key sizes - SUPPORTED_ALGORITHMS = { - KeyBlobEncryptionAlgorithm.AES_CBC: [128, 192, 256], - KeyBlobEncryptionAlgorithm.SM4_CBC: [128], - } - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - header = pack( - LITTLE_ENDIAN + UINT8 + UINT16 + UINT8, - self.KEYBLOB_VERSION, - 8 + len(self.key), - self.KEYBLOB_TAG, - ) - options = pack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT8 + UINT8, - 0x01, # Flags - DEK - len(self.key), - self.algorithm.tag, - 0, - ) - return header + options + self.key - - -class EleMessageGenerateKeyBLobOtfad(EleMessageGenerateKeyBlob): - """ELE Message Generate OTFAD KeyBlob.""" - - KEYBLOB_NAME = "OTFAD" - # List of supported algorithms and theirs key sizes - SUPPORTED_ALGORITHMS = {KeyBlobEncryptionAlgorithm.AES_CTR: [128]} - - def __init__( - self, - key_identifier: int, - key: bytes, - aes_counter: bytes, - start_address: int, - end_address: int, - read_only: bool = True, - decryption_enabled: bool = True, - configuration_valid: bool = True, - ) -> None: - """Constructor of generate OTFAD keyblob class. - - :param key_identifier: ID of Key - :param key: OTFAD key - :param aes_counter: AES counter value - :param start_address: Start address in memory to be encrypted - :param end_address: End address in memory to be encrypted - :param read_only: Read only flag, defaults to True - :param decryption_enabled: Decryption enable flag, defaults to True - :param configuration_valid: Configuration valid flag, defaults to True - """ - self.aes_counter = aes_counter - self.start_address = start_address - self.end_address = end_address - self.read_only = read_only - self.decryption_enabled = decryption_enabled - self.configuration_valid = configuration_valid - super().__init__(key_identifier, KeyBlobEncryptionAlgorithm.AES_CTR, key) - - def validate(self) -> None: - """Validate generate OTFAD keyblob.""" - # Validate general members - super().validate() - # 1 Validate OTFAD Key identifier - struct_index = self.key_id & 0xFF - peripheral_index = (self.key_id >> 8) & 0xFF - reserved = self.key_id & 0xFFFF0000 - - if struct_index > 3: - raise SPSDKValueError( - "Invalid OTFAD Key Identifier. Byte 0 must be in range [0-3]," - " to select used key struct, for proper scrambling." - ) - - if peripheral_index not in [1, 2]: - raise SPSDKValueError( - "Invalid OTFAD Key Identifier. Byte 1 must be in range [1-2]," - " to select used peripheral [FlexSPIx]." - ) - - if reserved != 0: - raise SPSDKValueError( - "Invalid OTFAD Key Identifier. Byte 2-3 must be set to 0." - ) - - # 2. validate AES counter - if len(self.aes_counter) != 8: - raise SPSDKValueError("Invalid AES counter length. It must be 64 bits.") - - # 3. start address - if self.start_address != 0 and self.start_address != align( - self.start_address, 1024 - ): - raise SPSDKValueError( - "Invalid OTFAD start address. Start address has to be aligned to 1024 bytes." - ) - - # 4. end address - if self.end_address != 0 and self.end_address != align(self.end_address, 1024): - raise SPSDKValueError( - "Invalid OTFAD end address. End address has to be aligned to 1024 bytes." - ) - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - header = pack( - LITTLE_ENDIAN + UINT8 + UINT16 + UINT8, - self.KEYBLOB_VERSION, - 0x30, - self.KEYBLOB_TAG, - ) - options = pack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT8 + UINT8, - 0x02, # Flags - OTFAD - 0x28, - self.algorithm.tag, - 0, - ) - end_address = self.end_address - if self.read_only: - end_address |= 0x04 - if self.decryption_enabled: - end_address |= 0x02 - if self.configuration_valid: - end_address |= 0x01 - - otfad_config = pack( - LITTLE_ENDIAN + "16s" + "8s" + UINT32 + UINT32 + UINT32, - self.key, - self.aes_counter, - self.start_address, - end_address, - 0, - ) - crc32_function = mkPredefinedCrcFun("crc-32-mpeg") - crc: int = crc32_function(otfad_config) - return ( - header + options + otfad_config + crc.to_bytes(4, Endianness.LITTLE.value) - ) - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - ret = super().info() - ret += f"AES Counter: {self.aes_counter.hex()}\n" - ret += f"Start address: {self.start_address:08x}\n" - ret += f"End address: {self.end_address:08x}\n" - ret += f"Read_only: {self.read_only}\n" - ret += f"Enabled: {self.decryption_enabled}\n" - ret += f"Valid: {self.configuration_valid}\n" - return ret - - -class EleMessageGenerateKeyBlobIee(EleMessageGenerateKeyBlob): - """ELE Message Generate IEE KeyBlob.""" - - KEYBLOB_NAME = "IEE" - # List of supported algorithms and theirs key sizes - SUPPORTED_ALGORITHMS = { - KeyBlobEncryptionAlgorithm.AES_XTS: [256, 512], - KeyBlobEncryptionAlgorithm.AES_CTR: [128, 256], - } - - def __init__( - self, - key_identifier: int, - algorithm: KeyBlobEncryptionAlgorithm, - key: bytes, - ctr_mode: KeyBlobEncryptionIeeCtrModes, - aes_counter: bytes, - page_offset: int, - region_number: int, - bypass: bool = False, - locked: bool = False, - ) -> None: - """Constructor of generate IEE keyblob class. - - :param key_identifier: ID of key - :param algorithm: Used algorithm - :param key: IEE key - :param ctr_mode: In case of AES CTR algorithm, the CTR mode must be selected - :param aes_counter: AES counter in case of AES CTR algorithm - :param page_offset: IEE page offset - :param region_number: Region number - :param bypass: Encryption bypass flag, defaults to False - :param locked: Locked flag, defaults to False - """ - self.ctr_mode = ctr_mode - self.aes_counter = aes_counter - self.page_offset = page_offset - self.region_number = region_number - self.bypass = bypass - self.locked = locked - super().__init__(key_identifier, algorithm, key) - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - header = pack( - LITTLE_ENDIAN + UINT8 + UINT16 + UINT8, - self.KEYBLOB_VERSION, - 88, - self.KEYBLOB_TAG, - ) - options = pack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT8 + UINT8, - 0x03, # Flags - IEE - len(self.key), - self.algorithm.tag, - 0, - ) - region_attribute = 0 - if self.bypass: - region_attribute |= 1 << 7 - if self.algorithm == KeyBlobEncryptionAlgorithm.AES_XTS: - region_attribute |= 0b01 << 4 - if len(self.key) == 64: - region_attribute |= 0x01 - else: - region_attribute |= self.ctr_mode.tag << 4 - if len(self.key) == 32: - region_attribute |= 0x01 - - if self.algorithm == KeyBlobEncryptionAlgorithm.AES_CTR: - key1 = align_block(self.key, 32, 0) - key2 = align_block(self.aes_counter, 32, 0) - else: - key_len = len(self.key) - key1 = align_block(self.key[: key_len // 2], 32, 0) - key2 = align_block(self.key[key_len // 2 :], 32, 0) - - lock_options = pack( - LITTLE_ENDIAN + UINT8 + UINT8 + UINT16, - self.region_number, - 0x01 if self.locked else 0x00, - 0, - ) - - iee_config = pack( - LITTLE_ENDIAN + UINT32 + UINT32 + "32s" + "32s" + "4s", - region_attribute, - self.page_offset, - key1, - key2, - lock_options, - ) - crc32_function = mkPredefinedCrcFun("crc-32-mpeg") - crc: int = crc32_function(iee_config) - return header + options + iee_config + crc.to_bytes(4, Endianness.LITTLE.value) - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - if self.algorithm == KeyBlobEncryptionAlgorithm.AES_CTR: - key1 = align_block(self.key, 32, 0) - key2 = align_block(self.aes_counter, 32, 0) - else: - key_len = len(self.key) - key1 = align_block(self.key[: key_len // 2], 32, 0) - key2 = align_block(self.key[key_len // 2 :], 32, 0) - ret = super().info() - if self.algorithm == KeyBlobEncryptionAlgorithm.AES_CTR: - ret += f"AES Counter mode:{KeyBlobEncryptionIeeCtrModes.get_description(self.ctr_mode.tag)}\n" - ret += f"AES Counter: {self.aes_counter.hex()}\n" - ret += f"Key1: {key1.hex()}\n" - ret += f"Key2: {key2.hex()}\n" - ret += f"Page offset: {self.page_offset:08x}\n" - ret += f"Region number: {self.region_number:02x}\n" - ret += f"Bypass: {self.bypass}\n" - ret += f"Locked: {self.locked}\n" - return ret - - -class EleMessageLoadKeyBLob(EleMessage): - """ELE Message Load KeyBlob.""" - - CMD = MessageIDs.LOAD_KEY_BLOB_REQ.tag - COMMAND_PAYLOAD_WORDS_COUNT = 3 - - def __init__(self, key_identifier: int, keyblob: bytes) -> None: - """Constructor of Load Key Blob class. - - :param key_identifier: ID of key - :param keyblob: Keyblob to be wrapped - """ - super().__init__() - self.key_id = key_identifier - - self.keyblob = keyblob - self.validate() - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - payload = pack( - LITTLE_ENDIAN + UINT32 + UINT32 + UINT32, - self.key_id, - 0, - self.command_data_address, - ) - payload = self.header_export() + payload - return payload - - @property - def command_data(self) -> bytes: - """Command data to be loaded into target memory space.""" - return self.keyblob - - def info(self) -> str: - """Print information including live data. - - :return: Information about the message. - """ - ret = super().info() - ret += "\n" - ret += f"Key ID: {self.key_id}\n" - ret += f"KeyBlob size: {len(self.keyblob)}\n" - return ret - - -class EleMessageWriteFuse(EleMessage): - """Write Fuse request.""" - - CMD = MessageIDs.WRITE_FUSE.tag - COMMAND_PAYLOAD_WORDS_COUNT = 2 - - def __init__( - self, bit_position: int, bit_length: int, lock: bool, payload: int - ) -> None: - """Constructor. - - This command allows to write to the fuses. - OEM Fuses are accessible depending on the chip lifecycle. - - :param bit_position: Fuse identifier expressed as its position in bit in the fuse map. - :param bit_length: Number of bits to be written. - :param lock: Write lock requirement. When set to 1, fuse words are locked. When unset, no write lock is done. - :param payload: Data to be written - """ - super().__init__() - self.bit_position = bit_position - self.bit_length = bit_length - self.lock = lock - self.payload = payload - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - - ret += pack( - LITTLE_ENDIAN + UINT16 + UINT16 + UINT32, - self.bit_position, - self.bit_length | 0x8000 if self.lock else 0, - self.payload, - ) - return ret - - -class EleMessageWriteShadowFuse(EleMessage): - """Write shadow fuse request.""" - - CMD = MessageIDs.WRITE_SHADOW_FUSE.tag - COMMAND_PAYLOAD_WORDS_COUNT = 2 - - def __init__(self, index: int, value: int) -> None: - """Constructor. - - This command allows to write to the shadow fuses. - - :param index: Fuse identifier expressed as its position in bit in the fuse map. - :param value: Data to be written. - """ - super().__init__() - self.index = index - self.value = value - - def export(self) -> bytes: - """Exports message to final bytes array. - - :return: Bytes representation of message object. - """ - ret = self.header_export() - - ret += pack( - LITTLE_ENDIAN + UINT32 + UINT32, - self.index, - self.value, - ) - return ret - - -class EleMessageEnableApc(EleMessage): - """Enable APC (Application core) ELE Message.""" - - CMD = MessageIDs.ELE_ENABLE_APC_REQ.tag - - -class EleMessageEnableRtc(EleMessage): - """Enable RTC (Real time core) ELE Message.""" - - CMD = MessageIDs.ELE_ENABLE_RTC_REQ.tag - - -class EleMessageResetApcContext(EleMessage): - """Send request to reset APC context ELE Message.""" - - CMD = MessageIDs.ELE_RESET_APC_CTX_REQ.tag diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/__init__.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/__init__.py deleted file mode 100644 index ae89a397..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""This module contains AHAB related code.""" diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_abstract_interfaces.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_abstract_interfaces.py deleted file mode 100644 index 0e7356a2..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_abstract_interfaces.py +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2022-2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""AHAB abstract classes.""" - -from struct import calcsize, unpack -from typing import Tuple - -from typing_extensions import Self - -from ...exceptions import SPSDKLengthError, SPSDKParsingError, SPSDKValueError -from ...utils.abstract import BaseClass -from ...utils.misc import check_range - -LITTLE_ENDIAN = "<" -UINT8 = "B" -UINT16 = "H" -UINT32 = "L" -UINT64 = "Q" -RESERVED = 0 - - -class Container(BaseClass): - """Base class for any container.""" - - @classmethod - def fixed_length(cls) -> int: - """Returns the length of a container which is fixed. - - i.e. part of a container holds fixed values, whereas some entries have - variable length. - """ - return calcsize(cls.format()) - - def __len__(self) -> int: - """Returns the total length of a container. - - The length includes the fixed as well as the variable length part. - """ - return self.fixed_length() - - def __repr__(self) -> str: - return "Base AHAB Container class: " + self.__class__.__name__ - - def __str__(self) -> str: - raise NotImplementedError( - "__str__() is not implemented in base AHAB container class" - ) - - def export(self) -> bytes: - """Serialize object into bytes array.""" - raise NotImplementedError( - "export() is not implemented in base AHAB container class" - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Deserialize object from bytes array.""" - raise NotImplementedError( - "parse() is not implemented in base AHAB container class" - ) - - @classmethod - def format(cls) -> str: - """Returns the container data format as defined by struct package. - - The base returns only endianness (LITTLE_ENDIAN). - """ - return LITTLE_ENDIAN - - @classmethod - def _check_fixed_input_length(cls, binary: bytes) -> None: - """Checks the data length and container fixed length. - - This is just a helper function used throughout the code. - - :param Binary: Binary input data. - :raises SPSDKLengthError: If containers length is larger than data length. - """ - data_len = len(binary) - fixed_input_len = cls.fixed_length() - if data_len < fixed_input_len: - raise SPSDKLengthError( - f"Parsing error in fixed part of {cls.__name__} data!\n" - f"Input data must be at least {fixed_input_len} bytes!" - ) - - -class HeaderContainer(Container): - """A container with first byte defined as header - tag, length and version. - - Every "container" in AHAB consists of a header - tag, length and version. - - The only exception is the 'image array' or 'image array entry' respectively - which has no header at all and SRK record, which has 'signing algorithm' - instead of version. But this can be considered as a sort of SRK record - 'version'. - """ - - TAG = 0x00 - VERSION = 0x00 - - def __init__(self, tag: int, length: int, version: int): - """Class object initialized. - - :param tag: container tag. - :param length: container length. - :param version: container version. - """ - self.length = length - self.tag = tag - self.version = version - - def __eq__(self, other: object) -> bool: - if isinstance(other, (HeaderContainer, HeaderContainerInversed)): - if ( - self.tag == other.tag - and self.length == other.length - and self.version == other.version - ): - return True - - return False - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return super().format() + UINT8 + UINT16 + UINT8 - - def validate_header(self) -> None: - """Validates the header of container properties... - - i.e. tag e <0; 255>, otherwise an exception is raised. - :raises SPSDKValueError: Any MAndatory field has invalid value. - """ - if self.tag is None or not check_range(self.tag, end=0xFF): - raise SPSDKValueError( - f"AHAB: Head of Container: Invalid TAG Value: {self.tag}" - ) - if self.length is None or not check_range(self.length, end=0xFFFF): - raise SPSDKValueError( - f"AHAB: Head of Container: Invalid Length Value: {self.length}" - ) - if self.version is None or not check_range(self.version, end=0xFF): - raise SPSDKValueError( - f"AHAB: Head of Container: Invalid Version Value: {self.version}" - ) - - @classmethod - def parse_head(cls, binary: bytes) -> Tuple[int, int, int]: - """Parse binary data to get head members. - - :param binary: Binary data. - :raises SPSDKLengthError: Binary data length is not enough. - :return: Tuple with TAG, LENGTH, VERSION - """ - if len(binary) < 4: - raise SPSDKLengthError( - f"Parsing error in {cls.__name__} container head data!\n" - "Input data must be at least 4 bytes!" - ) - (version, length, tag) = unpack(HeaderContainer.format(), binary) - return tag, length, version - - @classmethod - def check_container_head(cls, binary: bytes) -> None: - """Compares the data length and container length. - - This is just a helper function used throughout the code. - - :param binary: Binary input data. - :raises SPSDKLengthError: If containers length is larger than data length. - :raises SPSDKParsingError: If containers header value doesn't match. - """ - cls._check_fixed_input_length(binary) - data_len = len(binary) - (tag, length, version) = cls.parse_head( - binary[: HeaderContainer.fixed_length()] - ) - - if ( - isinstance(cls.TAG, int) - and tag != cls.TAG - or isinstance(cls.TAG, list) - and not tag in cls.TAG - ): - raise SPSDKParsingError( - f"Parsing error of {cls.__name__} data!\n" - f"Invalid TAG {hex(tag)} loaded, expected {hex(cls.TAG)}!" - ) - - if data_len < length: - raise SPSDKLengthError( - f"Parsing error of {cls.__name__} data!\n" - f"At least {length} bytes expected, got {data_len} bytes!" - ) - - if ( - isinstance(cls.VERSION, int) - and version != cls.VERSION - or isinstance(cls.VERSION, list) - and not version in cls.VERSION - ): - raise SPSDKParsingError( - f"Parsing error of {cls.__name__} data!\n" - f"Invalid VERSION {version} loaded, expected {cls.VERSION}!" - ) - - -class HeaderContainerInversed(HeaderContainer): - """A container with first byte defined as header - tag, length and version. - - It same as "HeaderContainer" only the tag/length/version are in reverse order in binary form. - """ - - @classmethod - def parse_head(cls, binary: bytes) -> Tuple[int, int, int]: - """Parse binary data to get head members. - - :param binary: Binary data. - :raises SPSDKLengthError: Binary data length is not enough. - :return: Tuple with TAG, LENGTH, VERSION - """ - if len(binary) < 4: - raise SPSDKLengthError( - f"Parsing error in {cls.__name__} container head data!\n" - "Input data must be at least 4 bytes!" - ) - # Only SRK Table has splitted tag and version in binary format - (tag, length, version) = unpack(HeaderContainer.format(), binary) - return tag, length, version diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_container.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_container.py deleted file mode 100644 index 487addf4..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/ahab_container.py +++ /dev/null @@ -1,3885 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2021-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Implementation of raw AHAB container support. - -This module represents a generic AHAB container implementation. You can set the -containers values at will. From this perspective, consult with your reference -manual of your device for allowed values. -""" -# pylint: disable=too-many-lines -import logging -import math -import os -from struct import calcsize, pack, unpack -from typing import Any, Dict, List, Optional, Tuple, Union - -from typing_extensions import Self - -from ... import version as spsdk_version -from ...crypto.hash import EnumHashAlgorithm, get_hash -from ...crypto.keys import ( - IS_OSCCA_SUPPORTED, - EccCurve, - PublicKey, - PublicKeyEcc, - PublicKeyRsa, - PublicKeySM2, -) -from ...crypto.signature_provider import SignatureProvider, get_signature_provider -from ...crypto.symmetric import ( - aes_cbc_decrypt, - aes_cbc_encrypt, - sm4_cbc_decrypt, - sm4_cbc_encrypt, -) -from ...crypto.types import SPSDKEncoding -from ...crypto.utils import extract_public_key, get_matching_key_id -from ...ele.ele_constants import KeyBlobEncryptionAlgorithm -from ...exceptions import ( - SPSDKError, - SPSDKLengthError, - SPSDKParsingError, - SPSDKValueError, -) -from ...image.ahab.ahab_abstract_interfaces import ( - Container, - HeaderContainer, - HeaderContainerInversed, -) -from ...utils.database import DatabaseManager, get_db, get_families -from ...utils.images import BinaryImage -from ...utils.misc import ( - BinaryPattern, - Endianness, - align, - align_block, - check_range, - extend_block, - find_file, - load_binary, - load_configuration, - load_hex_string, - reverse_bytes_in_longs, - value_to_bytes, - value_to_int, - write_file, -) -from ...utils.schema_validator import CommentedConfig, check_config -from ...utils.spsdk_enum import SpsdkEnum - -logger = logging.getLogger(__name__) - -LITTLE_ENDIAN = "<" -UINT8 = "B" -UINT16 = "H" -UINT32 = "L" -UINT64 = "Q" -RESERVED = 0 -CONTAINER_ALIGNMENT = 8 -START_IMAGE_ADDRESS = 0x2000 -START_IMAGE_ADDRESS_NAND = 0x1C00 - - -TARGET_MEMORY_SERIAL_DOWNLOADER = "serial_downloader" -TARGET_MEMORY_NOR = "nor" -TARGET_MEMORY_NAND_4K = "nand_4k" -TARGET_MEMORY_NAND_2K = "nand_2k" - -TARGET_MEMORY_BOOT_OFFSETS = { - TARGET_MEMORY_SERIAL_DOWNLOADER: 0x400, - TARGET_MEMORY_NOR: 0x1000, - TARGET_MEMORY_NAND_4K: 0x400, - TARGET_MEMORY_NAND_2K: 0x400, -} - - -class AHABTags(SpsdkEnum): - """AHAB container related tags.""" - - BLOB = (0x81, "Blob (Wrapped Data Encryption Key).") - CONTAINER_HEADER = (0x87, "Container header.") - SIGNATURE_BLOCK = (0x90, "Signature block.") - CERTIFICATE_UUID = (0xA0, "Certificate with UUID.") - CERTIFICATE_NON_UUID = (0xAF, "Certificate without UUID.") - SRK_TABLE = (0xD7, "SRK table.") - SIGNATURE = (0xD8, "Signature part of signature block.") - SRK_RECORD = (0xE1, "SRK record.") - - -class AHABCoreId(SpsdkEnum): - """AHAB cored IDs.""" - - UNDEFINED = (0, "undefined", "Undefined core") - CORTEX_M33 = (1, "cortex-m33", "Cortex M33") - CORTEX_M4 = (2, "cortex-m4", "Cortex M4") - CORTEX_M7 = (2, "cortex-m7", "Cortex M7") - CORTEX_A55 = (2, "cortex-a55", "Cortex A55") - CORTEX_M4_1 = (3, "cortex-m4_1", "Cortex M4 alternative") - CORTEX_A53 = (4, "cortex-a53", "Cortex A53") - CORTEX_A35 = (4, "cortex-a35", "Cortex A35") - CORTEX_A72 = (5, "cortex-a72", "Cortex A72") - SECO = (6, "seco", "EL enclave") - HDMI_TX = (7, "hdmi-tx", "HDMI Tx") - HDMI_RX = (8, "hdmi-rx", "HDMI Rx") - V2X_1 = (9, "v2x-1", "V2X 1") - V2X_2 = (10, "v2x-2", "V2X 2") - - -def get_key_by_val(dictionary: Dict, val: Any) -> Any: - """Get Dictionary key by its value or default. - - :param dictionary: Dictionary to search in. - :param val: Value to search - :raises SPSDKValueError: In case that dictionary doesn't contains the value. - :return: Key. - """ - for key, value in dictionary.items(): - if value == val: - return key - raise SPSDKValueError( - f"The requested value [{val}] in dictionary [{dictionary}] is not available." - ) - - -class ImageArrayEntry(Container): - """Class representing image array entry as part of image array in the AHAB container. - - Image Array Entry content:: - - +-----+---------------------------------------------------------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+---------------------------------------------------------------+ - |0x00 | Image Offset | - +-----+---------------------------------------------------------------+ - |0x04 | Image Size | - +-----+---------------------------------------------------------------+ - |0x08 | | - |-----+ Load Address (64 bits) | - |0x0C | | - +-----+---------------------------------------------------------------+ - |0x10 | | - |-----+ Entry Point (64 bits) | - |0x14 | | - +-----+---------------------------------------------------------------+ - |0x18 | Flags | - +-----+---------------------------------------------------------------+ - |0x1C | Image meta data | - +-----+---------------------------------------------------------------+ - |0x20 | | - |-----+ Hash (512 bits) | - |.... | | - +-----+---------------------------------------------------------------+ - |0x60 | IV (256 bits) | - +-----+---------------------------------------------------------------+ - - """ - - IMAGE_OFFSET_LEN = 4 - IMAGE_SIZE_LEN = 4 - LOAD_ADDRESS_LEN = 8 - ENTRY_POINT_ADDRESS_LEN = 8 - FLAGS_LEN = 4 - IMAGE_META_DATA_LEN = 4 - HASH_LEN = 64 - IV_LEN = 32 - FLAGS_TYPE_OFFSET = 0 - FLAGS_TYPE_SIZE = 4 - FLAGS_TYPES = { - "csf": 0x01, - "scd": 0x02, - "executable": 0x03, - "data": 0x04, - "dcd_image": 0x05, - "seco": 0x06, - "provisioning_image": 0x07, - "dek_validation_fcb_chk": 0x08, - "provisioning_data": 0x09, - "executable_fast_boot_image": 0x0A, - "v2x_primary": 0x0B, - "v2x_secondary": 0x0C, - "v2x_rom_patch": 0x0D, - "v2x_dummy": 0x0E, - } - FLAGS_CORE_ID_OFFSET = 4 - FLAGS_CORE_ID_SIZE = 4 - FLAGS_HASH_OFFSET = 8 - FLAGS_HASH_SIZE = 3 - FLAGS_IS_ENCRYPTED_OFFSET = 11 - FLAGS_IS_ENCRYPTED_SIZE = 1 - FLAGS_BOOT_FLAGS_OFFSET = 16 - FLAGS_BOOT_FLAGS_SIZE = 15 - METADATA_START_CPU_ID_OFFSET = 0 - METADATA_START_CPU_ID_SIZE = 10 - METADATA_MU_CPU_ID_OFFSET = 10 - METADATA_MU_CPU_ID_SIZE = 10 - METADATA_START_PARTITION_ID_OFFSET = 20 - METADATA_START_PARTITION_ID_SIZE = 8 - - IMAGE_ALIGNMENTS = { - TARGET_MEMORY_SERIAL_DOWNLOADER: 512, - TARGET_MEMORY_NOR: 1024, - TARGET_MEMORY_NAND_2K: 2048, - TARGET_MEMORY_NAND_4K: 4096, - } - - def __init__( - self, - parent: "AHABContainer", - image: Optional[bytes] = None, - image_offset: int = 0, - load_address: int = 0, - entry_point: int = 0, - flags: int = 0, - image_meta_data: int = 0, - image_hash: Optional[bytes] = None, - image_iv: Optional[bytes] = None, - already_encrypted_image: bool = False, - ) -> None: - """Class object initializer. - - :param parent: Parent AHAB Container object. - :param image: Image in bytes. - :param image_offset: Offset in bytes from start of container to beginning of image. - :param load_address: Address the image is written to in memory (absolute address in system memory map). - :param entry_point: Entry point of image (absolute address). Only valid for executable image types. - For other image types the value is irrelevant. - :param flags: flags. - :param image_meta_data: image meta-data. - :param image_hash: SHA of image (512 bits) in big endian. Left - aligned and padded with zeroes for hash sizes below 512 bits. - :param image_iv: SHA256 of plain text image (256 bits) in big endian. - :param already_encrypted_image: The input image is already encrypted. - Used only for encrypted images. - """ - self._image_offset = 0 - self.parent = parent - self.flags = flags - self.already_encrypted_image = already_encrypted_image - self.image = image if image else b"" - self.image_offset = image_offset - self.image_size = self._get_valid_size(self.image) - self.load_address = load_address - self.entry_point = entry_point - self.image_meta_data = image_meta_data - self.image_hash = image_hash - self.image_iv = ( - image_iv or get_hash(self.plain_image, algorithm=EnumHashAlgorithm.SHA256) - if self.flags_is_encrypted - else bytes(self.IV_LEN) - ) - - @property - def _ahab_container(self) -> "AHABContainer": - """AHAB Container object.""" - return self.parent - - @property - def _ahab_image(self) -> "AHABImage": - """AHAB Image object.""" - return self._ahab_container.parent - - @property - def image_offset(self) -> int: - """Image offset.""" - return self._image_offset + self._ahab_container.container_offset - - @image_offset.setter - def image_offset(self, offset: int) -> None: - """Image offset. - - :param offset: Image offset. - """ - self._image_offset = offset - self._ahab_container.container_offset - - @property - def image_offset_real(self) -> int: - """Real offset in Bootable image.""" - target_memory = self._ahab_image.target_memory - return self.image_offset + TARGET_MEMORY_BOOT_OFFSETS[target_memory] - - def __eq__(self, other: object) -> bool: - if isinstance(other, ImageArrayEntry): - if ( - self.image_offset # pylint: disable=too-many-boolean-expressions - == other.image_offset - and self.image_size == other.image_size - and self.load_address == other.load_address - and self.entry_point == other.entry_point - and self.flags == other.flags - and self.image_meta_data == other.image_meta_data - and self.image_hash == other.image_hash - and self.image_iv == other.image_iv - ): - return True - - return False - - def __repr__(self) -> str: - return f"AHAB Image Array Entry, load address({hex(self.load_address)})" - - def __str__(self) -> str: - return ( - "AHAB Image Array Entry:\n" - f" Image size: {self.image_size}B\n" - f" Image offset in table: {hex(self.image_offset)}\n" - f" Image offset real: {hex(self.image_offset_real)}\n" - f" Entry point: {hex(self.entry_point)}\n" - f" Load address: {hex(self.load_address)}\n" - f" Flags: {hex(self.flags)})\n" - f" Meta data: {hex(self.image_meta_data)})\n" - f" Image hash: {self.image_hash.hex() if self.image_hash else 'Not available'})\n" - f" Image IV: {self.image_iv.hex()})\n" - ) - - @property - def image(self) -> bytes: - """Image data for this Image array entry. - - The class decide by flags if encrypted of plain data has been returned. - - :raises SPSDKError: Invalid Image - Image is not encrypted yet. - :return: Image bytes. - """ - # if self.flags_is_encrypted and not self.already_encrypted_image: - # raise SPSDKError("Image is NOT encrypted, yet.") - - if self.flags_is_encrypted and self.already_encrypted_image: - return self.encrypted_image - return self.plain_image - - @image.setter - def image(self, data: bytes) -> None: - """Image data for this Image array entry. - - The class decide by flags if encrypted of plain data has been stored. - """ - input_image = align_block( - data, 16 if self.flags_is_encrypted else 4, padding=RESERVED - ) # align to encryptable block - self.plain_image = input_image if not self.already_encrypted_image else b"" - self.encrypted_image = input_image if self.already_encrypted_image else b"" - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() # endianness from base class - + UINT32 # Image Offset - + UINT32 # Image Size - + UINT64 # Load Address - + UINT64 # Entry Point - + UINT32 # Flags - + UINT32 # Image Meta Data - + "64s" # HASH - + "32s" # Input Vector - ) - - def update_fields(self) -> None: - """Updates the image fields in container based on provided image.""" - # self.image = align_block(self.image, self.get_valid_alignment(), 0) - self.image_size = self._get_valid_size(self.image) - algorithm = self.get_hash_from_flags(self.flags) - self.image_hash = extend_block( - get_hash(self.image, algorithm=algorithm), - self.HASH_LEN, - padding=0, - ) - if not self.image_iv and self.flags_is_encrypted: - self.image_iv = get_hash( - self.plain_image, algorithm=EnumHashAlgorithm.SHA256 - ) - - @staticmethod - def create_meta( - start_cpu_id: int = 0, mu_cpu_id: int = 0, start_partition_id: int = 0 - ) -> int: - """Create meta data field. - - :param start_cpu_id: ID of CPU to start, defaults to 0 - :param mu_cpu_id: ID of MU for selected CPU to start, defaults to 0 - :param start_partition_id: ID of partition to start, defaults to 0 - :return: Image meta data field. - """ - meta_data = start_cpu_id - meta_data |= mu_cpu_id << 10 - meta_data |= start_partition_id << 20 - return meta_data - - @staticmethod - def create_flags( - image_type: str = "executable", - core_id: AHABCoreId = AHABCoreId.CORTEX_M33, - hash_type: EnumHashAlgorithm = EnumHashAlgorithm.SHA256, - is_encrypted: bool = False, - boot_flags: int = 0, - ) -> int: - """Create flags field. - - :param image_type: Type of image, defaults to "executable" - :param core_id: Core ID, defaults to "cortex-m33" - :param hash_type: Hash type, defaults to sha256 - :param is_encrypted: Is image encrypted, defaults to False - :param boot_flags: Boot flags controlling the SCFW boot, defaults to 0 - :return: Image flags data field. - """ - flags_data = ImageArrayEntry.FLAGS_TYPES[image_type] - flags_data |= core_id.tag << ImageArrayEntry.FLAGS_CORE_ID_OFFSET - flags_data |= { - EnumHashAlgorithm.SHA256: 0x0, - EnumHashAlgorithm.SHA384: 0x1, - EnumHashAlgorithm.SHA512: 0x2, - EnumHashAlgorithm.SM3: 0x3, - }[hash_type] << ImageArrayEntry.FLAGS_HASH_OFFSET - flags_data |= ( - 1 << ImageArrayEntry.FLAGS_IS_ENCRYPTED_OFFSET if is_encrypted else 0 - ) - flags_data |= boot_flags << ImageArrayEntry.FLAGS_BOOT_FLAGS_OFFSET - - return flags_data - - @staticmethod - def get_hash_from_flags(flags: int) -> EnumHashAlgorithm: - """Get Hash algorithm name from flags. - - :param flags: Value of flags. - :return: Hash name. - """ - hash_val = (flags >> ImageArrayEntry.FLAGS_HASH_OFFSET) & ( - (1 << ImageArrayEntry.FLAGS_HASH_SIZE) - 1 - ) - return { - 0x00: EnumHashAlgorithm.SHA256, - 0x01: EnumHashAlgorithm.SHA384, - 0x02: EnumHashAlgorithm.SHA512, - 0x03: EnumHashAlgorithm.SM3, - }[hash_val] - - @property - def flags_image_type(self) -> str: - """Get Image type name from flags. - - :return: Image type name - """ - image_type_val = (self.flags >> ImageArrayEntry.FLAGS_TYPE_OFFSET) & ( - (1 << ImageArrayEntry.FLAGS_TYPE_SIZE) - 1 - ) - try: - return get_key_by_val(ImageArrayEntry.FLAGS_TYPES, image_type_val) - except SPSDKValueError: - return f"Unknown Image Type {image_type_val}" - - @property - def flags_core_id(self) -> int: - """Get Core ID from flags. - - :return: Core ID - """ - return (self.flags >> ImageArrayEntry.FLAGS_CORE_ID_OFFSET) & ( - (1 << ImageArrayEntry.FLAGS_CORE_ID_SIZE) - 1 - ) - - @property - def flags_is_encrypted(self) -> bool: - """Get Is encrypted property from flags. - - :return: True if is encrypted, false otherwise - """ - return bool( - (self.flags >> ImageArrayEntry.FLAGS_IS_ENCRYPTED_OFFSET) - & ((1 << ImageArrayEntry.FLAGS_IS_ENCRYPTED_SIZE) - 1) - ) - - @property - def flags_boot_flags(self) -> int: - """Get boot flags property from flags. - - :return: Boot flags - """ - return (self.flags >> ImageArrayEntry.FLAGS_BOOT_FLAGS_OFFSET) & ( - (1 << ImageArrayEntry.FLAGS_BOOT_FLAGS_SIZE) - 1 - ) - - @property - def metadata_start_cpu_id(self) -> int: - """Get CPU ID property from Meta data. - - :return: Start CPU ID - """ - return ( - self.image_meta_data >> ImageArrayEntry.METADATA_START_CPU_ID_OFFSET - ) & ((1 << ImageArrayEntry.METADATA_START_CPU_ID_SIZE) - 1) - - @property - def metadata_mu_cpu_id(self) -> int: - """Get Start CPU Memory Unit ID property from Meta data. - - :return: Start CPU MU ID - """ - return (self.image_meta_data >> ImageArrayEntry.METADATA_MU_CPU_ID_OFFSET) & ( - (1 << ImageArrayEntry.METADATA_MU_CPU_ID_SIZE) - 1 - ) - - @property - def metadata_start_partition_id(self) -> int: - """Get Start Partition ID property from Meta data. - - :return: Start Partition ID - """ - return ( - self.image_meta_data >> ImageArrayEntry.METADATA_START_PARTITION_ID_OFFSET - ) & ((1 << ImageArrayEntry.METADATA_START_PARTITION_ID_SIZE) - 1) - - def export(self) -> bytes: - """Serializes container object into bytes in little endian. - - The hash and IV are kept in big endian form. - - :return: bytes representing container content. - """ - # hash: fixed at 512 bits, left aligned and padded with zeros for hash below 512 bits. - # In case the hash is shorter, the pack() (in little endian mode) should grant, that the - # hash is left aligned and padded with zeros due to the '64s' formatter. - # iv: fixed at 256 bits. - data = pack( - self.format(), - self._image_offset, - self.image_size, - self.load_address, - self.entry_point, - self.flags, - self.image_meta_data, - self.image_hash, - self.image_iv, - ) - - return data - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - if self.image is None or self._get_valid_size(self.image) != self.image_size: - raise SPSDKValueError("Image Entry: Invalid Image binary.") - if self.image_offset is None or not check_range( - self.image_offset, end=(1 << 32) - 1 - ): - raise SPSDKValueError( - f"Image Entry: Invalid Image Offset: {self.image_offset}" - ) - if self.image_size is None or not check_range( - self.image_size, end=(1 << 32) - 1 - ): - raise SPSDKValueError(f"Image Entry: Invalid Image Size: {self.image_size}") - if self.load_address is None or not check_range( - self.load_address, end=(1 << 64) - 1 - ): - raise SPSDKValueError( - f"Image Entry: Invalid Image Load address: {self.load_address}" - ) - if self.entry_point is None or not check_range( - self.entry_point, end=(1 << 64) - 1 - ): - raise SPSDKValueError( - f"Image Entry: Invalid Image Entry point: {self.entry_point}" - ) - if self.flags is None or not check_range(self.flags, end=(1 << 32) - 1): - raise SPSDKValueError(f"Image Entry: Invalid Image Flags: {self.flags}") - if self.image_meta_data is None or not check_range( - self.image_meta_data, end=(1 << 32) - 1 - ): - raise SPSDKValueError( - f"Image Entry: Invalid Image Meta data: {self.image_meta_data}" - ) - if ( - self.image_hash is None - or not any(self.image_hash) - or len(self.image_hash) != self.HASH_LEN - ): - raise SPSDKValueError("Image Entry: Invalid Image Hash.") - - @classmethod - def parse(cls, data: bytes, parent: "AHABContainer") -> Self: # type: ignore # pylint: disable=arguments-differ - """Parse input binary chunk to the container object. - - :param parent: Parent AHABContainer object. - :param data: Binary data with Image Array Entry block to parse. - :raises SPSDKLengthError: If invalid length of image is detected. - :raises SPSDKValueError: Invalid hash for image. - :return: Object recreated from the binary data. - """ - binary_size = len(data) - # Just updates offsets from AHAB Image start As is feature of none xip containers - ImageArrayEntry._check_fixed_input_length(data) - ( - image_offset, - image_size, - load_address, - entry_point, - flags, - image_meta_data, - image_hash, - image_iv, - ) = unpack(ImageArrayEntry.format(), data[: ImageArrayEntry.fixed_length()]) - - iae = cls( - parent=parent, - image_offset=0, - image=None, - load_address=load_address, - entry_point=entry_point, - flags=flags, - image_meta_data=image_meta_data, - image_hash=image_hash, - image_iv=image_iv, - already_encrypted_image=bool( - (flags >> ImageArrayEntry.FLAGS_IS_ENCRYPTED_OFFSET) - & ((1 << ImageArrayEntry.FLAGS_IS_ENCRYPTED_SIZE) - 1) - ), - ) - iae._image_offset = image_offset - - iae_offset = ( - AHABContainer.fixed_length() - + parent.image_array_len * ImageArrayEntry.fixed_length() - + parent.container_offset - ) - - logger.debug( - ( - "Parsing Image array Entry:\n" - f"Image offset: {hex(iae.image_offset)}\n" - f"Image offset raw: {hex(iae._image_offset)}\n" - f"Image offset real: {hex(iae.image_offset_real)}" - ) - ) - if iae.image_offset + image_size - iae_offset > binary_size: - raise SPSDKLengthError( - "Container data image is out of loaded binary:" - f"Image entry record has end of image at {hex(iae.image_offset + image_size - iae_offset)}," - f" but the loaded image length has only {hex(binary_size)}B size." - ) - image = data[ - iae.image_offset - iae_offset : iae.image_offset - iae_offset + image_size - ] - image_hash_cmp = extend_block( - get_hash(image, algorithm=ImageArrayEntry.get_hash_from_flags(flags)), - ImageArrayEntry.HASH_LEN, - padding=0, - ) - if image_hash != image_hash_cmp: - raise SPSDKValueError("Parsed Container data image has invalid HASH!") - iae.image = image - return iae - - @staticmethod - def load_from_config( - parent: "AHABContainer", config: Dict[str, Any] - ) -> "ImageArrayEntry": - """Converts the configuration option into an AHAB image array entry object. - - "config" content of container configurations. - - :param parent: Parent AHABContainer object. - :param config: Configuration of ImageArray. - :return: Container Header Image Array Entry object. - """ - image_path = config.get("image_path") - search_paths = parent.search_paths - assert isinstance(image_path, str) - is_encrypted = config.get("is_encrypted", False) - meta_data = ImageArrayEntry.create_meta( - value_to_int(config.get("meta_data_start_cpu_id", 0)), - value_to_int(config.get("meta_data_mu_cpu_id", 0)), - value_to_int(config.get("meta_data_start_partition_id", 0)), - ) - image_data = load_binary(image_path, search_paths=search_paths) - flags = ImageArrayEntry.create_flags( - image_type=config.get("image_type", "executable"), - core_id=AHABCoreId.from_label(config.get("core_id", "cortex-m33")), - hash_type=EnumHashAlgorithm.from_label(config.get("hash_type", "sha256")), - is_encrypted=is_encrypted, - boot_flags=value_to_int(config.get("boot_flags", 0)), - ) - return ImageArrayEntry( - parent=parent, - image=image_data, - image_offset=value_to_int(config.get("image_offset", 0)), - load_address=value_to_int(config.get("load_address", 0)), - entry_point=value_to_int(config.get("entry_point", 0)), - flags=flags, - image_meta_data=meta_data, - image_iv=None, # IV data are updated by UpdateFields function - ) - - def create_config( - self, index: int, image_index: int, data_path: str - ) -> Dict[str, Any]: - """Create configuration of the AHAB Image data blob. - - :param index: Container index. - :param image_index: Data Image index. - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - ret_cfg: Dict[str, Union[str, int, bool]] = {} - image_name = "N/A" - if self.plain_image: - image_name = ( - f"container{index}_image{image_index}_{self.flags_image_type}.bin" - ) - write_file(self.plain_image, os.path.join(data_path, image_name), "wb") - if self.encrypted_image: - image_name_encrypted = f"container{index}_image{image_index}_{self.flags_image_type}_encrypted.bin" - write_file( - self.encrypted_image, - os.path.join(data_path, image_name_encrypted), - "wb", - ) - if image_name == "N/A": - image_name = image_name_encrypted - - ret_cfg["image_path"] = image_name - ret_cfg["image_offset"] = hex(self.image_offset) - ret_cfg["load_address"] = hex(self.load_address) - ret_cfg["entry_point"] = hex(self.entry_point) - ret_cfg["image_type"] = self.flags_image_type - core_ids = self.parent.parent._database.get_dict( - DatabaseManager.AHAB, "core_ids" - ) - ret_cfg["core_id"] = core_ids.get( - self.flags_core_id, f"Unknown ID: {self.flags_core_id}" - ) - ret_cfg["is_encrypted"] = bool(self.flags_is_encrypted) - ret_cfg["boot_flags"] = self.flags_boot_flags - ret_cfg["meta_data_start_cpu_id"] = self.metadata_start_cpu_id - ret_cfg["meta_data_mu_cpu_id"] = self.metadata_mu_cpu_id - ret_cfg["meta_data_start_partition_id"] = self.metadata_start_partition_id - ret_cfg["hash_type"] = self.get_hash_from_flags(self.flags).label - - return ret_cfg - - def get_valid_alignment(self) -> int: - """Get valid alignment for AHAB container and memory target. - - :return: AHAB valid alignment - """ - if ( - self.flags_image_type == "seco" - and self.parent.parent.target_memory == TARGET_MEMORY_SERIAL_DOWNLOADER - ): - return 4 - - return max([self.IMAGE_ALIGNMENTS[self._ahab_image.target_memory], 1024]) - - def _get_valid_size(self, image: Optional[bytes]) -> int: - """Get valid image size that will be stored. - - :return: AHAB valid image size - """ - if not image: - return 0 - return align(len(image), 4 if self.flags_image_type == "seco" else 1) - - def get_valid_offset(self, original_offset: int) -> int: - """Get valid offset for AHAB container. - - :param original_offset: Offset that should be updated to valid one - :return: AHAB valid offset - """ - alignment = self.get_valid_alignment() - alignment = max( - alignment, - self.parent.parent._database.get_int( - DatabaseManager.AHAB, "valid_offset_minimal_alignment", 4 - ), - ) - return align(original_offset, alignment) - - -class SRKRecord(HeaderContainerInversed): - """Class representing SRK (Super Root Key) record as part of SRK table in the AHAB container. - - The class holds information about RSA/ECDSA signing algorithms. - - SRK Record:: - - +-----+---------------------------------------------------------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+---------------------------------------------------------------+ - |0x00 | Tag | Length of SRK | Signing Algo | - +-----+---------------------------------------------------------------+ - |0x04 | Hash Algo | Key Size/Curve | Not Used | SRK Flags | - +-----+---------------------------------------------------------------+ - |0x08 | RSA modulus len / ECDSA X len | RSA exponent len / ECDSA Y len| - +-----+---------------------------------------------------------------+ - |0x0C | RSA modulus (big endian) / ECDSA X (big endian) | - +-----+---------------------------------------------------------------+ - |... | RSA exponent (big endian) / ECDSA Y (big endian) | - +-----+---------------------------------------------------------------+ - - """ - - TAG = AHABTags.SRK_RECORD.tag - VERSION = [0x21, 0x27, 0x28] # type: ignore - VERSION_ALGORITHMS = {"rsa": 0x21, "ecdsa": 0x27, "sm2": 0x28} - HASH_ALGORITHM = { - EnumHashAlgorithm.SHA256: 0x0, - EnumHashAlgorithm.SHA384: 0x1, - EnumHashAlgorithm.SHA512: 0x2, - EnumHashAlgorithm.SM3: 0x3, - } - ECC_KEY_TYPE = { - EccCurve.SECP521R1: 0x3, - EccCurve.SECP384R1: 0x2, - EccCurve.SECP256R1: 0x1, - } - RSA_KEY_TYPE = {2048: 0x5, 4096: 0x7} - SM2_KEY_TYPE = 0x8 - KEY_SIZES = { - 0x1: (32, 32), - 0x2: (48, 48), - 0x3: (66, 66), - 0x5: (128, 128), - 0x7: (256, 256), - 0x8: (32, 32), - } - - FLAGS_CA_MASK = 0x80 - - def __init__( - self, - src_key: Optional[PublicKey] = None, - signing_algorithm: str = "rsa", - hash_type: EnumHashAlgorithm = EnumHashAlgorithm.SHA256, - key_size: int = 0, - srk_flags: int = 0, - crypto_param1: bytes = b"", - crypto_param2: bytes = b"", - ): - """Class object initializer. - - :param src_key: Optional source public key used to create the SRKRecord - :param signing_algorithm: signing algorithm type. - :param hash_type: hash algorithm type. - :param key_size: key (curve) size. - :param srk_flags: flags. - :param crypto_param1: RSA modulus (big endian) or ECDSA X (big endian) - :param crypto_param2: RSA exponent (big endian) or ECDSA Y (big endian) - """ - super().__init__( - tag=self.TAG, length=-1, version=self.VERSION_ALGORITHMS[signing_algorithm] - ) - self.signing_algorithm = signing_algorithm - self.src_key = src_key - self.hash_algorithm = self.HASH_ALGORITHM[hash_type] - self.key_size = key_size - self.srk_flags = srk_flags - self.crypto_param1 = crypto_param1 - self.crypto_param2 = crypto_param2 - - def __eq__(self, other: object) -> bool: - if isinstance(other, SRKRecord): - if ( - super().__eq__(other) # pylint: disable=too-many-boolean-expressions - and self.hash_algorithm == other.hash_algorithm - and self.key_size == other.key_size - and self.srk_flags == other.srk_flags - and self.crypto_param1 == other.crypto_param1 - and self.crypto_param2 == other.crypto_param2 - ): - return True - - return False - - def __len__(self) -> int: - return super().__len__() + len(self.crypto_param1) + len(self.crypto_param2) - - def __repr__(self) -> str: - return f"AHAB SRK record, key: {self.get_key_name()}" - - def __str__(self) -> str: - return ( - "AHAB SRK Record:\n" - f" Key: {self.get_key_name()}\n" - f" SRK flags: {hex(self.srk_flags)}\n" - f" Param 1 value: {self.crypto_param1.hex()})\n" - f" Param 2 value: {self.crypto_param2.hex()})\n" - ) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() - + UINT8 # Hash Algorithm - + UINT8 # Key Size / Curve - + UINT8 # Not Used - + UINT8 # SRK Flags - + UINT16 # crypto_param2_len - + UINT16 # crypto_param1_len - ) - - def update_fields(self) -> None: - """Update all fields depended on input values.""" - self.length = len(self) - - def export(self) -> bytes: - """Export one SRK record, little big endian format. - - The crypto parameters (X/Y for ECDSA or modulus/exponent) are kept in - big endian form. - - :return: bytes representing container content. - """ - return ( - pack( - self.format(), - self.tag, - self.length, - self.version, - self.hash_algorithm, - self.key_size, - RESERVED, - self.srk_flags, - len(self.crypto_param1), - len(self.crypto_param2), - ) - + self.crypto_param1 - + self.crypto_param2 - ) - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - self.validate_header() - if self.hash_algorithm is None or not check_range(self.hash_algorithm, end=2): - raise SPSDKValueError( - f"SRK record: Invalid Hash algorithm: {self.hash_algorithm}" - ) - - if self.srk_flags is None or not check_range(self.srk_flags, end=0xFF): - raise SPSDKValueError(f"SRK record: Invalid Flags: {self.srk_flags}") - - if self.version == 0x21: # Signing algorithm RSA - if self.key_size not in self.RSA_KEY_TYPE.values(): - raise SPSDKValueError( - f"SRK record: Invalid Key size in match to RSA signing algorithm: {self.key_size}" - ) - elif self.version == 0x27: # Signing algorithm ECDSA - if self.key_size not in self.ECC_KEY_TYPE.values(): - raise SPSDKValueError( - f"SRK record: Invalid Key size in match to ECDSA signing algorithm: {self.key_size}" - ) - elif self.version == 0x28: # Signing algorithm SM2 - if self.key_size != self.SM2_KEY_TYPE: - raise SPSDKValueError( - f"SRK record: Invalid Key size in match to SM2 signing algorithm: {self.key_size}" - ) - else: - raise SPSDKValueError( - f"SRK record: Invalid Signing algorithm: {self.version}" - ) - - # Check lengths - - if ( - self.crypto_param1 is None - or len(self.crypto_param1) != self.KEY_SIZES[self.key_size][0] - ): - raise SPSDKValueError( - f"SRK record: Invalid Crypto parameter 1: 0x{self.crypto_param1.hex()}" - ) - - if ( - self.crypto_param2 is None - or len(self.crypto_param2) != self.KEY_SIZES[self.key_size][1] - ): - raise SPSDKValueError( - f"SRK record: Invalid Crypto parameter 2: 0x{self.crypto_param2.hex()}" - ) - - computed_length = ( - self.fixed_length() - + self.KEY_SIZES[self.key_size][0] - + self.KEY_SIZES[self.key_size][1] - ) - if self.length != len(self) or self.length != computed_length: - raise SPSDKValueError( - f"SRK record: Invalid Length: Length of SRK:{self.length}" - f", Computed Length of SRK:{computed_length}" - ) - - @staticmethod - def create_from_key(public_key: PublicKey, srk_flags: int = 0) -> "SRKRecord": - """Create instance from key data. - - :param public_key: Loaded public key. - :param srk_flags: SRK flags for key. - :raises SPSDKValueError: Unsupported keys size is detected. - """ - if isinstance(public_key, PublicKeyRsa): - par_n: int = public_key.public_numbers.n - par_e: int = public_key.public_numbers.e - key_size = SRKRecord.RSA_KEY_TYPE[public_key.key_size] - return SRKRecord( - src_key=public_key, - signing_algorithm="rsa", - hash_type=EnumHashAlgorithm.SHA256, - key_size=key_size, - srk_flags=srk_flags, - crypto_param1=par_n.to_bytes( - length=SRKRecord.KEY_SIZES[key_size][0], - byteorder=Endianness.BIG.value, - ), - crypto_param2=par_e.to_bytes( - length=SRKRecord.KEY_SIZES[key_size][1], - byteorder=Endianness.BIG.value, - ), - ) - - elif isinstance(public_key, PublicKeyEcc): - par_x: int = public_key.x - par_y: int = public_key.y - key_size = SRKRecord.ECC_KEY_TYPE[public_key.curve] - - if not public_key.key_size in [256, 384, 521]: - raise SPSDKValueError( - f"Unsupported ECC key for AHAB container: {public_key.key_size}" - ) - hash_type = { - 256: EnumHashAlgorithm.SHA256, - 384: EnumHashAlgorithm.SHA384, - 521: EnumHashAlgorithm.SHA512, - }[public_key.key_size] - - return SRKRecord( - signing_algorithm="ecdsa", - hash_type=hash_type, - key_size=key_size, - srk_flags=srk_flags, - crypto_param1=par_x.to_bytes( - length=SRKRecord.KEY_SIZES[key_size][0], - byteorder=Endianness.BIG.value, - ), - crypto_param2=par_y.to_bytes( - length=SRKRecord.KEY_SIZES[key_size][1], - byteorder=Endianness.BIG.value, - ), - ) - - assert isinstance( - public_key, PublicKeySM2 - ), "Unsupported public key for SRK record" - param1: bytes = value_to_bytes( - "0x" + public_key.public_numbers[:64], byte_cnt=32 - ) - param2: bytes = value_to_bytes( - "0x" + public_key.public_numbers[64:], byte_cnt=32 - ) - assert len(param1 + param2) == 64, "Invalid length of the SM2 key" - key_size = SRKRecord.SM2_KEY_TYPE - return SRKRecord( - src_key=public_key, - signing_algorithm="sm2", - hash_type=EnumHashAlgorithm.SM3, - key_size=key_size, - srk_flags=srk_flags, - crypto_param1=param1, - crypto_param2=param2, - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with SRK record block to parse. - :raises SPSDKLengthError: Invalid length of SRK record data block. - :return: SRK record recreated from the binary data. - """ - SRKRecord.check_container_head(data) - ( - _, # tag - container_length, - signing_algo, - hash_algo, - key_size_curve, - _, # reserved - srk_flags, - crypto_param1_len, - crypto_param2_len, - ) = unpack(SRKRecord.format(), data[: SRKRecord.fixed_length()]) - - # Although we know from the total length, that we have enough bytes, - # the crypto param lengths may be set improperly and we may get into trouble - # while parsing. So we need to check the lengths as well. - param_length = SRKRecord.fixed_length() + crypto_param1_len + crypto_param2_len - if container_length < param_length: - raise SPSDKLengthError( - "Parsing error of SRK Record data." - "SRK record lengths mismatch. Sum of lengths declared in container " - f"({param_length} (= {SRKRecord.fixed_length()} + {crypto_param1_len} + " - f"{crypto_param2_len})) doesn't match total length declared in container ({container_length})!" - ) - crypto_param1 = data[ - SRKRecord.fixed_length() : SRKRecord.fixed_length() + crypto_param1_len - ] - crypto_param2 = data[ - SRKRecord.fixed_length() - + crypto_param1_len : SRKRecord.fixed_length() - + crypto_param1_len - + crypto_param2_len - ] - - return cls( - signing_algorithm=get_key_by_val( - SRKRecord.VERSION_ALGORITHMS, signing_algo - ), - hash_type=get_key_by_val(SRKRecord.HASH_ALGORITHM, hash_algo), - key_size=key_size_curve, - srk_flags=srk_flags, - crypto_param1=crypto_param1, - crypto_param2=crypto_param2, - ) - - def get_key_name(self) -> str: - """Get text key name in SRK record. - - :return: Key name. - """ - if get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "rsa": - return f"rsa{get_key_by_val(self.RSA_KEY_TYPE, self.key_size)}" - if get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "ecdsa": - return get_key_by_val(self.ECC_KEY_TYPE, self.key_size) - if get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "sm2": - return "sm2" - return "Unknown Key name!" - - def get_public_key(self, encoding: SPSDKEncoding = SPSDKEncoding.PEM) -> bytes: - """Store the SRK public key as a file. - - :param encoding: Public key encoding style, default is PEM. - :raises SPSDKError: Unsupported public key - """ - par1 = int.from_bytes(self.crypto_param1, Endianness.BIG.value) - par2 = int.from_bytes(self.crypto_param2, Endianness.BIG.value) - key: Union[PublicKey, PublicKeyEcc, PublicKeyRsa, PublicKeySM2] - if get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "rsa": - # RSA Key to store - key = PublicKeyRsa.recreate(par1, par2) - elif get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "ecdsa": - # ECDSA Key to store - curve = get_key_by_val(self.ECC_KEY_TYPE, self.key_size) - key = PublicKeyEcc.recreate(par1, par2, curve=curve) - elif ( - get_key_by_val(self.VERSION_ALGORITHMS, self.version) == "sm2" - and IS_OSCCA_SUPPORTED - ): - encoding = SPSDKEncoding.DER - key = PublicKeySM2.recreate(self.crypto_param1 + self.crypto_param2) - - return key.export(encoding=encoding) - - -class SRKTable(HeaderContainerInversed): - """Class representing SRK (Super Root Key) table in the AHAB container as part of signature block. - - SRK Table:: - - +-----+---------------------------------------------------------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+---------------------------------------------------------------+ - |0x00 | Tag | Length of SRK Table | Version | - +-----+---------------------------------------------------------------+ - |0x04 | SRK Record 1 | - +-----+---------------------------------------------------------------+ - |... | SRK Record 2 | - +-----+---------------------------------------------------------------+ - |... | SRK Record 3 | - +-----+---------------------------------------------------------------+ - |... | SRK Record 4 | - +-----+---------------------------------------------------------------+ - - """ - - TAG = AHABTags.SRK_TABLE.tag - VERSION = 0x42 - SRK_RECORDS_CNT = 4 - - def __init__(self, srk_records: Optional[List[SRKRecord]] = None) -> None: - """Class object initializer. - - :param srk_records: list of SRKRecord objects. - """ - super().__init__(tag=self.TAG, length=-1, version=self.VERSION) - self._srk_records: List[SRKRecord] = srk_records or [] - self.length = len(self) - - def __repr__(self) -> str: - return f"AHAB SRK TABLE, keys count: {len(self._srk_records)}" - - def __str__(self) -> str: - return ( - "AHAB SRK table:\n" - f" Keys count: {len(self._srk_records)}\n" - f" Length: {self.length}\n" - f"SRK table HASH: {self.compute_srk_hash().hex()}" - ) - - def clear(self) -> None: - """Clear the SRK Table Object.""" - self._srk_records.clear() - self.length = -1 - - def add_record(self, public_key: PublicKey, srk_flags: int = 0) -> None: - """Add SRK table record. - - :param public_key: Loaded public key. - :param srk_flags: SRK flags for key. - """ - self._srk_records.append( - SRKRecord.create_from_key(public_key=public_key, srk_flags=srk_flags) - ) - self.length = len(self) - - def __eq__(self, other: object) -> bool: - """Compares for equality with other SRK Table objects. - - :param other: object to compare with. - :return: True on match, False otherwise. - """ - if isinstance(other, SRKTable): - if super().__eq__(other) and self._srk_records == other._srk_records: - return True - - return False - - def __len__(self) -> int: - records_len = 0 - for record in self._srk_records: - records_len += len(record) - return super().__len__() + records_len - - def update_fields(self) -> None: - """Update all fields depended on input values.""" - for rec in self._srk_records: - rec.update_fields() - self.length = len(self) - - def compute_srk_hash(self) -> bytes: - """Computes a SHA256 out of all SRK records. - - :return: SHA256 computed over SRK records. - """ - return get_hash(data=self.export(), algorithm=EnumHashAlgorithm.SHA256) - - def get_source_keys(self) -> List[PublicKey]: - """Return list of source public keys. - - Either from the src_key field or recreate them. - :return: List of public keys. - """ - ret = [] - for srk in self._srk_records: - if srk.src_key: - # return src key if available - ret.append(srk.src_key) - else: - # recreate the key - ret.append(PublicKey.parse(srk.get_public_key())) - return ret - - def export(self) -> bytes: - """Serializes container object into bytes in little endian. - - :return: bytes representing container content. - """ - data = pack(self.format(), self.tag, self.length, self.version) - - for srk_record in self._srk_records: - data += srk_record.export() - - return data - - def validate(self, data: Dict[str, Any]) -> None: - """Validate object data. - - :param data: Additional validation data. - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - self.validate_header() - if self._srk_records is None or len(self._srk_records) != self.SRK_RECORDS_CNT: - raise SPSDKValueError( - f"SRK table: Invalid SRK records: {self._srk_records}" - ) - - # Validate individual SRK records - for srk_rec in self._srk_records: - srk_rec.validate() - - # Check if all SRK records has same type - srk_records_info = [ - (x.version, x.hash_algorithm, x.key_size, x.length, x.srk_flags) - for x in self._srk_records - ] - - messages = [ - "Signing algorithm", - "Hash algorithm", - "Key Size", - "Length", - "Flags", - ] - for i in range(4): - if not all(srk_records_info[0][i] == x[i] for x in srk_records_info): - raise SPSDKValueError( - f"SRK table: SRK records haven't same {messages[i]}: {[x[i] for x in srk_records_info]}" - ) - - if "srkh_sha_supports" in data.keys(): - if ( - get_key_by_val( - SRKRecord.HASH_ALGORITHM, self._srk_records[0].hash_algorithm - ).label - not in data["srkh_sha_supports"] - ): - raise SPSDKValueError( - "SRK table: SRK records haven't supported hash algorithm:" - f" Used:{self._srk_records[0].hash_algorithm} is not member of" - f" {data['srkh_sha_supports']}" - ) - # Check container length - if self.length != len(self): - raise SPSDKValueError( - f"SRK table: Invalid Length of SRK table: {self.length} != {len(self)}" - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with SRK table block to parse. - :raises SPSDKLengthError: Invalid length of SRK table data block. - :return: Object recreated from the binary data. - """ - SRKTable.check_container_head(data) - srk_rec_offset = SRKTable.fixed_length() - _, container_length, _ = unpack(SRKTable.format(), data[:srk_rec_offset]) - if ((container_length - srk_rec_offset) % SRKTable.SRK_RECORDS_CNT) != 0: - raise SPSDKLengthError("SRK table: Invalid length of SRK records data.") - srk_rec_size = math.ceil( - (container_length - srk_rec_offset) / SRKTable.SRK_RECORDS_CNT - ) - - # try to parse records - srk_records: List[SRKRecord] = [] - for _ in range(SRKTable.SRK_RECORDS_CNT): - srk_record = SRKRecord.parse(data[srk_rec_offset:]) - srk_rec_offset += srk_rec_size - srk_records.append(srk_record) - - return cls(srk_records=srk_records) - - def create_config(self, index: int, data_path: str) -> Dict[str, Any]: - """Create configuration of the AHAB Image SRK Table. - - :param index: Container Index. - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - ret_cfg: Dict[str, Union[List, bool]] = {} - cfg_srks = [] - - ret_cfg["flag_ca"] = bool( - self._srk_records[0].srk_flags & SRKRecord.FLAGS_CA_MASK - ) - - for ix_srk, srk in enumerate(self._srk_records): - filename = ( - f"container{index}_srk_public_key{ix_srk}_{srk.get_key_name()}.PEM" - ) - write_file( - data=srk.get_public_key(), - path=os.path.join(data_path, filename), - mode="wb", - ) - cfg_srks.append(filename) - - ret_cfg["srk_array"] = cfg_srks - return ret_cfg - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "SRKTable": - """Converts the configuration option into an AHAB image object. - - "config" content of container configurations. - - :param config: array of AHAB containers configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: SRK Table object. - """ - srk_table = SRKTable() - flags = 0 - flag_ca = config.get("flag_ca", False) - if flag_ca: - flags |= SRKRecord.FLAGS_CA_MASK - srk_list = config.get("srk_array") - assert isinstance(srk_list, list) - for srk_key in srk_list: - assert isinstance(srk_key, str) - srk_key_path = find_file(srk_key, search_paths=search_paths) - srk_table.add_record(extract_public_key(srk_key_path), srk_flags=flags) - return srk_table - - -class ContainerSignature(HeaderContainer): - """Class representing the signature in AHAB container as part of the signature block. - - Signature:: - - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Tag | Length (MSB) | Length (LSB) | Version | - +-----+--------------+--------------+----------------+----------------+ - |0x04 | Reserved | - +-----+---------------------------------------------------------------+ - |0x08 | Signature Data | - +-----+---------------------------------------------------------------+ - - """ - - TAG = AHABTags.SIGNATURE.tag - VERSION = 0x00 - - def __init__( - self, - signature_data: Optional[bytes] = None, - signature_provider: Optional[SignatureProvider] = None, - ) -> None: - """Class object initializer. - - :param signature_data: signature. - :param signature_provider: Signature provider use to sign the image. - """ - super().__init__(tag=self.TAG, length=-1, version=self.VERSION) - self._signature_data = signature_data or b"" - self.signature_provider = signature_provider - self.length = len(self) - - def __eq__(self, other: object) -> bool: - if isinstance(other, ContainerSignature): - if super().__eq__(other) and self._signature_data == other._signature_data: - return True - - return False - - def __len__(self) -> int: - if ( - not self._signature_data or len(self._signature_data) == 0 - ) and self.signature_provider: - return super().__len__() + self.signature_provider.signature_length - - sign_data_len = len(self._signature_data) - if sign_data_len == 0: - return 0 - - return super().__len__() + sign_data_len - - def __repr__(self) -> str: - return "AHAB Container Signature" - - def __str__(self) -> str: - return ( - "AHAB Container Signature:\n" - f" Signature provider: {self.signature_provider.info() if self.signature_provider else 'Not available'}\n" - f" Signature: {self.signature_data.hex() if self.signature_data else 'Not available'}" - ) - - @property - def signature_data(self) -> bytes: - """Get the signature data. - - :return: signature data. - """ - return self._signature_data - - @signature_data.setter - def signature_data(self, value: bytes) -> None: - """Set the signature data. - - :param value: signature data. - """ - self._signature_data = value - self.length = len(self) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return super().format() + UINT32 # reserved - - def sign(self, data_to_sign: bytes) -> None: - """Sign the data_to_sign and store signature into class. - - :param data_to_sign: Data to be signed by store private key - :raises SPSDKError: Missing private key or raw signature data. - """ - if not self.signature_provider and len(self._signature_data) == 0: - raise SPSDKError( - "The Signature container doesn't have specified the private key to sign." - ) - - if self.signature_provider: - self._signature_data = self.signature_provider.get_signature(data_to_sign) - - def export(self) -> bytes: - """Export signature data that is part of Signature Block. - - :return: bytes representing container signature content. - """ - if len(self) == 0: - return b"" - - data = ( - pack( - self.format(), - self.version, - self.length, - self.tag, - RESERVED, - ) - + self._signature_data - ) - - return data - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - self.validate_header() - if self._signature_data is None or len(self._signature_data) < 20: - raise SPSDKValueError( - f"Signature: Invalid Signature data: 0x{self.signature_data.hex()}" - ) - if self.length != len(self): - raise SPSDKValueError( - f"Signature: Invalid Signature length: {self.length} != {len(self)}." - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with Container signature block to parse. - :return: Object recreated from the binary data. - """ - ContainerSignature.check_container_head(data) - fix_len = ContainerSignature.fixed_length() - - _, container_length, _, _ = unpack(ContainerSignature.format(), data[:fix_len]) - signature_data = data[fix_len:container_length] - - return cls(signature_data=signature_data) - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "ContainerSignature": - """Converts the configuration option into an AHAB image object. - - "config" content of container configurations. - - :param config: array of AHAB containers configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: Container signature object. - """ - signature_provider = get_signature_provider( - sp_cfg=config.get("signature_provider"), - local_file_key=config.get("signing_key"), - search_paths=search_paths, - ) - assert signature_provider - return ContainerSignature(signature_provider=signature_provider) - - -class Certificate(HeaderContainer): - """Class representing certificate in the AHAB container as part of the signature block. - - The Certificate comes in two forms - with and without UUID. - - Certificate format 1:: - - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Tag | Length (MSB) | Length (LSB) | Version | - +-----+--------------+--------------+----------------+----------------+ - |0x04 | Permissions | Perm (invert)| Signature offset | - +-----+--------------+--------------+---------------------------------+ - |0x08 | Public Key | - +-----+---------------------------------------------------------------+ - |... | Signature | - +-----+---------------------------------------------------------------+ - - Certificate format 2:: - - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Tag | Length (MSB) | Length (LSB) | Version | - +-----+--------------+--------------+----------------+----------------+ - |0x04 | Permissions | Perm (invert)| Signature offset | - +-----+--------------+--------------+---------------------------------+ - |0x08 | UUID | - +-----+---------------------------------------------------------------+ - |... | Public Key | - +-----+---------------------------------------------------------------+ - |... | Signature | - +-----+---------------------------------------------------------------+ - - """ - - TAG = [AHABTags.CERTIFICATE_UUID.tag, AHABTags.CERTIFICATE_NON_UUID.tag] # type: ignore - UUID_LEN = 16 - UUID_OFFSET = 0x08 - VERSION = 0x00 - PERM_NXP = { - "secure_enclave_debug": 0x02, - "hdmi_debug": 0x04, - "life_cycle": 0x10, - "hdcp_fuses": 0x20, - } - PERM_OEM = { - "container": 0x01, - "phbc_debug": 0x02, - "soc_debug_domain_1": 0x04, - "soc_debug_domain_2": 0x08, - "life_cycle": 0x10, - "monotonic_counter": 0x20, - } - PERM_SIZE = 8 - - def __init__( - self, - permissions: int = 0, - uuid: Optional[bytes] = None, - public_key: Optional[SRKRecord] = None, - signature_provider: Optional[SignatureProvider] = None, - ): - """Class object initializer. - - :param permissions: used to indicate what a certificate can be used for. - :param uuid: optional 128-bit unique identifier. - :param public_key: public Key. SRK record entry describing the key. - :param signature_provider: Signature provider for certificate. Signature is calculated over - all data from beginning of the certificate up to, but not including the signature. - """ - tag = ( - AHABTags.CERTIFICATE_UUID.tag if uuid else AHABTags.CERTIFICATE_NON_UUID.tag - ) - super().__init__(tag=tag, length=-1, version=self.VERSION) - self._permissions = permissions - self.signature_offset = -1 - self._uuid = uuid - self.public_key = public_key - self.signature = ContainerSignature( - signature_data=b"", signature_provider=signature_provider - ) - - def __eq__(self, other: object) -> bool: - if isinstance(other, Certificate): - if ( - super().__eq__(other) # pylint: disable=too-many-boolean-expressions - and self._permissions == other._permissions - and self.signature_offset == other.signature_offset - and self._uuid == other._uuid - and self.public_key == other.public_key - and self.signature == other.signature - ): - return True - - return False - - def __repr__(self) -> str: - return "AHAB Certificate" - - def __str__(self) -> str: - return ( - "AHAB Certificate:\n" - f" Permission: {hex(self._permissions)}\n" - f" UUID: {self._uuid.hex() if self._uuid else 'Not Available'}\n" - f" Public Key: {str(self.public_key) if self.public_key else 'Not available'}\n" - f" Signature: {str(self.signature) if self.signature else 'Not available'}" - ) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() # endianness, header: version, length, tag - + UINT16 # signature offset - + UINT8 # inverted permissions - + UINT8 # permissions - ) - - def __len__(self) -> int: - assert self.public_key - uuid_len = len(self._uuid) if self._uuid else 0 - return super().__len__() + uuid_len + len(self.public_key) + len(self.signature) - - @staticmethod - def create_permissions(permissions: List[str]) -> int: - """Create integer representation of permission field. - - :param permissions: List of string permissions. - :return: Integer representation of permissions. - """ - ret = 0 - permission_map = {} - permission_map.update(Certificate.PERM_NXP) - permission_map.update(Certificate.PERM_OEM) - for permission in permissions: - ret |= permission_map[permission] - - return ret - - @property - def permission_to_sign_container(self) -> bool: - """Certificate has permission to sign container.""" - return bool(self._permissions & self.PERM_OEM["container"]) - - def create_config_permissions(self, srk_set: str) -> List[str]: - """Create list of string representation of permission field. - - :param srk_set: SRK set to get proper string values. - :return: List of string representation of permissions. - """ - ret = [] - perm_maps = {"nxp": self.PERM_NXP, "oem": self.PERM_OEM} - perm_map = perm_maps.get(srk_set) - - for i in range(self.PERM_SIZE): - if self._permissions & (1 << i): - ret.append( - get_key_by_val(perm_map, 1 << i) - if perm_map and (1 << i) in perm_map.values() - else f"Unknown permission {hex(1< bytes: - """Returns binary data to be signed. - - The certificate block must be properly initialized, so the data are valid for - signing. There is signed whole certificate block without signature part. - - - :raises SPSDKValueError: if Signature Block or SRK Table is missing. - :return: bytes representing data to be signed. - """ - assert self.public_key - cert_data_to_sign = ( - pack( - self.format(), - self.version, - self.length, - self.tag, - self.signature_offset, - ~self._permissions & 0xFF, - self._permissions, - ) - + self.public_key.export() - ) - # if uuid is present, insert it into the cert data - if self._uuid: - cert_data_to_sign = ( - cert_data_to_sign[: self.UUID_OFFSET] - + self._uuid - + cert_data_to_sign[self.UUID_OFFSET :] - ) - - return cert_data_to_sign - - def update_fields(self) -> None: - """Update all fields depended on input values.""" - assert self.public_key - self.public_key.update_fields() - self.tag = ( - AHABTags.CERTIFICATE_UUID.tag - if self._uuid - else AHABTags.CERTIFICATE_NON_UUID.tag - ) - self.signature_offset = ( - super().__len__() - + (len(self._uuid) if self._uuid else 0) - + len(self.public_key) - ) - self.length = len(self) - self.signature.sign(self.get_signature_data()) - - def export(self) -> bytes: - """Export container certificate object into bytes. - - :return: bytes representing container content. - """ - assert self.public_key - cert = ( - pack( - self.format(), - self.version, - self.length, - self.tag, - self.signature_offset, - ~self._permissions & 0xFF, - self._permissions, - ) - + self.public_key.export() - + self.signature.export() - ) - # if uuid is present, insert it into the cert data - if self._uuid: - cert = cert[: self.UUID_OFFSET] + self._uuid + cert[self.UUID_OFFSET :] - assert self.length == len(cert) - return cert - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - self.validate_header() - if self._permissions is None or not check_range(self._permissions, end=0xFF): - raise SPSDKValueError( - f"Certificate: Invalid Permission data: {self._permissions}" - ) - if self.public_key is None: - raise SPSDKValueError("Certificate: Missing public key.") - self.public_key.validate() - - if not self.signature: - raise SPSDKValueError("Signature must be provided") - - self.signature.validate() - - expected_signature_offset = ( - super().__len__() - + (len(self._uuid) if self._uuid else 0) - + len(self.public_key) - ) - if self.signature_offset != expected_signature_offset: - raise SPSDKValueError( - f"Certificate: Invalid signature offset. " - f"{self.signature_offset} != {expected_signature_offset}" - ) - if self._uuid and len(self._uuid) != self.UUID_LEN: - raise SPSDKValueError( - f"Certificate: Invalid UUID size. {len(self._uuid)} != {self.UUID_LEN}" - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with Certificate block to parse. - :raises SPSDKValueError: Certificate permissions are invalid. - :return: Object recreated from the binary data. - """ - Certificate.check_container_head(data) - certificate_data_offset = Certificate.fixed_length() - image_format = Certificate.format() - ( - _, # version, - container_length, - tag, - signature_offset, - inverted_permissions, - permissions, - ) = unpack(image_format, data[:certificate_data_offset]) - - if inverted_permissions != ~permissions & 0xFF: - raise SPSDKValueError("Certificate parser: Invalid permissions record.") - - uuid = None - - if AHABTags.CERTIFICATE_UUID == tag: - uuid = data[ - certificate_data_offset : certificate_data_offset + Certificate.UUID_LEN - ] - certificate_data_offset += Certificate.UUID_LEN - - public_key = SRKRecord.parse(data[certificate_data_offset:]) - - signature = ContainerSignature.parse(data[signature_offset:container_length]) - - cert = cls( - permissions=permissions, - uuid=uuid, - public_key=public_key, - ) - cert.signature = signature - return cert - - def create_config( - self, index: int, data_path: str, srk_set: str = "oem" - ) -> Dict[str, Any]: - """Create configuration of the AHAB Image Certificate. - - :param index: Container Index. - :param data_path: Path to store the data files of configuration. - :param srk_set: SRK set to know how to create certificate permissions. - :return: Configuration dictionary. - """ - ret_cfg: Dict[str, Any] = {} - assert self.public_key - ret_cfg["permissions"] = self.create_config_permissions(srk_set) - if self._uuid: - ret_cfg["uuid"] = "0x" + self._uuid.hex() - filename = f"container{index}_certificate_public_key_{self.public_key.get_key_name()}.PEM" - write_file( - data=self.public_key.get_public_key(), - path=os.path.join(data_path, filename), - mode="wb", - ) - ret_cfg["public_key"] = filename - ret_cfg["signature_provider"] = "N/A" - - return ret_cfg - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Certificate": - """Converts the configuration option into an AHAB image signature block certificate object. - - "config" content of container configurations. - - :param config: array of AHAB containers configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: Certificate object. - """ - cert_permissions_list = config.get("permissions", []) - cert_uuid_raw = config.get("uuid") - cert_uuid = value_to_bytes(cert_uuid_raw) if cert_uuid_raw else None - cert_public_key_path = config.get("public_key") - assert isinstance(cert_public_key_path, str) - cert_public_key_path = find_file( - cert_public_key_path, search_paths=search_paths - ) - cert_public_key = extract_public_key(cert_public_key_path) - cert_srk_rec = SRKRecord.create_from_key(cert_public_key) - cert_signature_provider = get_signature_provider( - config.get("signature_provider"), - config.get("signing_key"), - search_paths=search_paths, - ) - return Certificate( - permissions=Certificate.create_permissions(cert_permissions_list), - uuid=cert_uuid, - public_key=cert_srk_rec, - signature_provider=cert_signature_provider, - ) - - @staticmethod - def get_validation_schemas() -> List[Dict[str, Any]]: - """Get list of validation schemas. - - :return: Validation list of schemas. - """ - return [ - DatabaseManager().db.get_schema_file(DatabaseManager.AHAB)[ - "ahab_certificate" - ] - ] - - @staticmethod - def generate_config_template() -> str: - """Generate AHAB configuration template. - - :return: Certificate configuration templates. - """ - yaml_data = CommentedConfig( - "Advanced High-Assurance Boot Certificate Configuration template.", - Certificate.get_validation_schemas(), - ).get_template() - - return yaml_data - - -class Blob(HeaderContainer): - """The Blob object used in Signature Container. - - Blob (DEK) content:: - - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Tag | Length (MSB) | Length (LSB) | Version | - +-----+--------------+--------------+----------------+----------------+ - |0x04 | Mode | Algorithm | Size | Flags | - +-----+--------------+--------------+----------------+----------------+ - |0x08 | Wrapped Key | - +-----+--------------+--------------+----------------+----------------+ - - """ - - TAG = AHABTags.BLOB.tag - VERSION = 0x00 - FLAGS = 0x80 # KEK key flag - SUPPORTED_KEY_SIZES = [128, 192, 256] - - def __init__( - self, - flags: int = 0x80, - size: int = 0, - algorithm: KeyBlobEncryptionAlgorithm = KeyBlobEncryptionAlgorithm.AES_CBC, - mode: int = 0, - dek: Optional[bytes] = None, - dek_keyblob: Optional[bytes] = None, - key_identifier: int = 0, - ) -> None: - """Class object initializer. - - :param flags: Keyblob flags - :param size: key size [128,192,256] - :param dek: DEK key - :param mode: DEK BLOB mode - :param algorithm: Encryption algorithm - :param dek_keyblob: DEK keyblob - :param key_identifier: Key identifier. Must be same as it was used for keyblob generation - """ - super().__init__(tag=self.TAG, length=56 + size // 8, version=self.VERSION) - self.mode = mode - self.algorithm = algorithm - self._size = size - self.flags = flags - self.dek = dek - self.dek_keyblob = dek_keyblob or b"" - self.key_identifier = key_identifier - - def __eq__(self, other: object) -> bool: - if isinstance(other, Blob): - if ( - super().__eq__(other) # pylint: disable=too-many-boolean-expressions - and self.mode == other.mode - and self.algorithm == other.algorithm - and self._size == other._size - and self.flags == other.flags - and self.dek_keyblob == other.dek_keyblob - and self.key_identifier == other.key_identifier - ): - return True - - return False - - def __repr__(self) -> str: - return "AHAB Blob" - - def __str__(self) -> str: - return ( - "AHAB Blob:\n" - f" Mode: {self.mode}\n" - f" Algorithm: {self.algorithm.label}\n" - f" Key Size: {self._size}\n" - f" Flags: {self.flags}\n" - f" Key identifier: {hex(self.key_identifier)}\n" - f" DEK keyblob: {self.dek_keyblob.hex() if self.dek_keyblob else 'N/A'}" - ) - - @staticmethod - def compute_keyblob_size(key_size: int) -> int: - """Compute Keyblob size. - - :param key_size: Input AES key size in bits - :return: Keyblob size in bytes. - """ - return (key_size // 8) + 48 - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() # endianness, header: tag, length, version - + UINT8 # mode - + UINT8 # algorithm - + UINT8 # size - + UINT8 # flags - ) - - def __len__(self) -> int: - # return super()._total_length() + len(self.dek_keyblob) - return self.length - - def export(self) -> bytes: - """Export Signature Block Blob. - - :return: bytes representing Signature Block Blob. - """ - blob = ( - pack( - self.format(), - self.version, - self.length, - self.tag, - self.flags, - self._size // 8, - self.algorithm.tag, - self.mode, - ) - + self.dek_keyblob - ) - - return blob - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of AHAB Blob - """ - self.validate_header() - - if self._size not in self.SUPPORTED_KEY_SIZES: - raise SPSDKValueError("AHAB Blob: Invalid key size.") - if self.mode is None: - raise SPSDKValueError("AHAB Blob: Invalid mode.") - if self.algorithm is None: - raise SPSDKValueError("AHAB Blob: Invalid algorithm.") - if self.dek and len(self.dek) != self._size // 8: - raise SPSDKValueError("AHAB Blob: Invalid DEK key size.") - if self.dek_keyblob is None or len( - self.dek_keyblob - ) != self.compute_keyblob_size(self._size): - raise SPSDKValueError("AHAB Blob: Invalid Wrapped key.") - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with Blob block to parse. - :return: Object recreated from the binary data. - """ - Blob.check_container_head(data) - ( - _, # version - container_length, - _, # tag - flags, - size, - algorithm, # algorithm - mode, # mode - ) = unpack(Blob.format(), data[: Blob.fixed_length()]) - - dek_keyblob = data[Blob.fixed_length() : container_length] - - return cls( - size=size * 8, - flags=flags, - dek_keyblob=dek_keyblob, - mode=mode, - algorithm=KeyBlobEncryptionAlgorithm.from_tag(algorithm), - ) - - def create_config(self, index: int, data_path: str) -> Dict[str, Any]: - """Create configuration of the AHAB Image Blob. - - :param index: Container Index. - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - ret_cfg: Dict[str, Any] = {} - assert self.dek_keyblob - filename = f"container{index}_dek_keyblob.bin" - write_file(self.export(), os.path.join(data_path, filename), "wb") - ret_cfg["dek_key_size"] = self._size - ret_cfg["dek_key"] = "N/A" - ret_cfg["dek_keyblob"] = filename - ret_cfg["key_identifier"] = self.key_identifier - - return ret_cfg - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Blob": - """Converts the configuration option into an AHAB image signature block blob object. - - "config" content of container configurations. - - :param config: Blob configuration - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKValueError: Invalid configuration - Invalid DEK KeyBlob - :return: Blob object. - """ - dek_size = value_to_int(config.get("dek_key_size", 128)) - dek_input = config.get("dek_key") - dek_keyblob_input = config.get("dek_keyblob") - key_identifier = config.get("key_identifier", 0) - assert dek_input, "Missing DEK value" - assert dek_keyblob_input, "Missing DEK KEYBLOB value" - - dek = load_hex_string(dek_input, dek_size // 8, search_paths) - dek_keyblob_value = load_hex_string( - dek_keyblob_input, Blob.compute_keyblob_size(dek_size) + 8, search_paths - ) - if not dek_keyblob_value: - raise SPSDKValueError("Invalid DEK KeyBlob.") - - keyblob = Blob.parse(dek_keyblob_value) - keyblob.dek = dek - keyblob.key_identifier = key_identifier - return keyblob - - def encrypt_data(self, iv: bytes, data: bytes) -> bytes: - """Encrypt data. - - :param iv: Initial vector 128 bits length - :param data: Data to encrypt - :raises SPSDKError: Missing DEK, unsupported algorithm - :return: Encrypted data - """ - if not self.dek: - raise SPSDKError("The AHAB keyblob hasn't defined DEK to encrypt data") - - encryption_methods = { - KeyBlobEncryptionAlgorithm.AES_CBC: aes_cbc_encrypt, - KeyBlobEncryptionAlgorithm.SM4_CBC: sm4_cbc_encrypt, - } - - if not encryption_methods.get(self.algorithm): - raise SPSDKError(f"Unsupported encryption algorithm: {self.algorithm}") - return encryption_methods[self.algorithm](self.dek, data, iv) - - def decrypt_data(self, iv: bytes, encrypted_data: bytes) -> bytes: - """Encrypt data. - - :param iv: Initial vector 128 bits length - :param encrypted_data: Data to decrypt - :raises SPSDKError: Missing DEK, unsupported algorithm - :return: Plain data - """ - if not self.dek: - raise SPSDKError("The AHAB keyblob hasn't defined DEK to encrypt data") - - decryption_methods = { - KeyBlobEncryptionAlgorithm.AES_CBC: aes_cbc_decrypt, - KeyBlobEncryptionAlgorithm.SM4_CBC: sm4_cbc_decrypt, - } - - if not decryption_methods.get(self.algorithm): - raise SPSDKError(f"Unsupported encryption algorithm: {self.algorithm}") - return decryption_methods[self.algorithm](self.dek, encrypted_data, iv) - - -class SignatureBlock(HeaderContainer): - """Class representing signature block in the AHAB container. - - Signature Block:: - - +---------------+----------------+----------------+----------------+-----+ - | Byte 3 | Byte 2 | Byte 1 | Byte 0 | Fix | - |---------------+----------------+----------------+----------------+ len | - | Tag | Length | Version | | - |---------------+---------------------------------+----------------+ | - | SRK Table Offset | Certificate Offset | | - |--------------------------------+---------------------------------+ | - | Blob Offset | Signature Offset | | - |--------------------------------+---------------------------------+ | - | Key identifier in case that Blob is present | | - +------------------------------------------------------------------+-----+ Starting offset - | SRK Table | | - +------------------------------------------------------------------+-----+ Padding length - | 64 bit alignment | | - +------------------------------------------------------------------+-----+ Starting offset - | Signature | | - +------------------------------------------------------------------+-----+ Padding length - | 64 bit alignment | | - +------------------------------------------------------------------+-----+ Starting offset - | Certificate | | - +------------------------------------------------------------------+-----+ Padding length - | 64 bit alignment | | - +------------------------------------------------------------------+-----+ Starting offset - | Blob | | - +------------------------------------------------------------------+-----+ - - """ - - TAG = AHABTags.SIGNATURE_BLOCK.tag - VERSION = 0x00 - - def __init__( - self, - srk_table: Optional["SRKTable"] = None, - container_signature: Optional["ContainerSignature"] = None, - certificate: Optional["Certificate"] = None, - blob: Optional["Blob"] = None, - ): - """Class object initializer. - - :param srk_table: SRK table. - :param container_signature: container signature. - :param certificate: container certificate. - :param blob: container blob. - """ - super().__init__(tag=self.TAG, length=-1, version=self.VERSION) - self._srk_table_offset = 0 - self._certificate_offset = 0 - self._blob_offset = 0 - self.signature_offset = 0 - self.srk_table = srk_table - self.signature = container_signature - self.certificate = certificate - self.blob = blob - - def __eq__(self, other: object) -> bool: - """Compares for equality with other Signature Block objects. - - :param other: object to compare with. - :return: True on match, False otherwise. - """ - if isinstance(other, SignatureBlock): - if ( - super().__eq__(other) # pylint: disable=too-many-boolean-expressions - and self._srk_table_offset == other._srk_table_offset - and self._certificate_offset == other._certificate_offset - and self._blob_offset == other._blob_offset - and self.signature_offset == other.signature_offset - and self.srk_table == other.srk_table - and self.signature == other.signature - and self.certificate == other.certificate - and self.blob == other.blob - ): - return True - - return False - - def __len__(self) -> int: - self.update_fields() - return self.length - - def __repr__(self) -> str: - return "AHAB Signature Block" - - def __str__(self) -> str: - return ( - "AHAB Signature Block:\n" - f" SRK Table: {bool(self.srk_table)}\n" - f" Certificate: {bool(self.certificate)}\n" - f" Signature: {bool(self.signature)}\n" - f" Blob: {bool(self.blob)}" - ) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() - + UINT16 # certificate offset - + UINT16 # SRK table offset - + UINT16 # signature offset - + UINT16 # blob offset - + UINT32 # key_identifier if blob is used - ) - - def update_fields(self) -> None: - """Update all fields depended on input values.""" - # 1: Update SRK Table - # Nothing to do with SRK Table - last_offset = 0 - last_block_size = align(calcsize(self.format()), CONTAINER_ALIGNMENT) - if self.srk_table: - self.srk_table.update_fields() - last_offset = self._srk_table_offset = last_offset + last_block_size - last_block_size = align(len(self.srk_table), CONTAINER_ALIGNMENT) - else: - self._srk_table_offset = 0 - - # 2: Update Signature (at least length) - # Nothing to do with Signature - in this time , it MUST be ready - if self.signature: - last_offset = self.signature_offset = last_offset + last_block_size - last_block_size = align(len(self.signature), CONTAINER_ALIGNMENT) - else: - self.signature_offset = 0 - # 3: Optionally update Certificate - if self.certificate: - self.certificate.update_fields() - last_offset = self._certificate_offset = last_offset + last_block_size - last_block_size = align(len(self.certificate), CONTAINER_ALIGNMENT) - else: - self._certificate_offset = 0 - # 4: Optionally update Blob - if self.blob: - last_offset = self._blob_offset = last_offset + last_block_size - last_block_size = align(len(self.blob), CONTAINER_ALIGNMENT) - else: - self._blob_offset = 0 - - # 5: Update length of Signature block - self.length = last_offset + last_block_size - - def export(self) -> bytes: - """Export Signature block. - - :raises SPSDKLengthError: if exported data length doesn't match container length. - :return: bytes signature block content. - """ - extended_header = pack( - self.format(), - self.version, - self.length, - self.tag, - self._certificate_offset, - self._srk_table_offset, - self.signature_offset, - self._blob_offset, - self.blob.key_identifier if self.blob else RESERVED, - ) - - signature_block = bytearray(len(self)) - signature_block[0 : self.fixed_length()] = extended_header - if self.srk_table: - signature_block[ - self._srk_table_offset : self._srk_table_offset + len(self.srk_table) - ] = self.srk_table.export() - if self.signature: - signature_block[ - self.signature_offset : self.signature_offset + len(self.signature) - ] = self.signature.export() - if self.certificate: - signature_block[ - self._certificate_offset : self._certificate_offset - + len(self.certificate) - ] = self.certificate.export() - if self.blob: - signature_block[ - self._blob_offset : self._blob_offset + len(self.blob) - ] = self.blob.export() - - return signature_block - - def validate(self, data: Dict[str, Any]) -> None: - """Validate object data. - - :param data: Additional validation data. - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - - def check_offset(name: str, min_offset: int, offset: int) -> None: - if offset < min_offset: - raise SPSDKValueError( - f"Signature Block: Invalid {name} offset: {offset} < minimal offset {min_offset}" - ) - if offset != align(offset, CONTAINER_ALIGNMENT): - raise SPSDKValueError( - f"Signature Block: Invalid {name} offset alignment: {offset} is not aligned to 64 bits!" - ) - - self.validate_header() - if self.length != len(self): - raise SPSDKValueError( - f"Signature Block: Invalid block length: {self.length} != {len(self)}" - ) - if bool(self._srk_table_offset) != bool(self.srk_table): - raise SPSDKValueError( - "Signature Block: Invalid setting of SRK table offset." - ) - if bool(self.signature_offset) != bool(self.signature): - raise SPSDKValueError( - "Signature Block: Invalid setting of Signature offset." - ) - if bool(self._certificate_offset) != bool(self.certificate): - raise SPSDKValueError( - "Signature Block: Invalid setting of Certificate offset." - ) - if bool(self._blob_offset) != bool(self.blob): - raise SPSDKValueError("Signature Block: Invalid setting of Blob offset.") - - min_offset = self.fixed_length() - if self.srk_table: - self.srk_table.validate(data) - check_offset("SRK table", min_offset, self._srk_table_offset) - min_offset = self._srk_table_offset + len(self.srk_table) - if self.signature: - self.signature.validate() - check_offset("Signature", min_offset, self.signature_offset) - min_offset = self.signature_offset + len(self.signature) - if self.certificate: - self.certificate.validate() - check_offset("Certificate", min_offset, self._certificate_offset) - min_offset = self._certificate_offset + len(self.certificate) - if self.blob: - self.blob.validate() - check_offset("Blob", min_offset, self._blob_offset) - min_offset = self._blob_offset + len(self.blob) - - if "flag_used_srk_id" in data.keys() and self.signature and self.srk_table: - public_keys = self.srk_table.get_source_keys() - if ( - self.signature.signature_provider - and self.certificate - and not self.certificate.permission_to_sign_container - ): - # Container is signed by SRK key. Get the matching key and verify that the private key - # belongs to the public key in SRK - srk_pair_id = get_matching_key_id( - public_keys, self.signature.signature_provider - ) - if srk_pair_id != data["flag_used_srk_id"]: - raise SPSDKValueError( - f"Signature Block: Configured SRK ID ({data['flag_used_srk_id']})" - f" doesn't match detected SRK ID for signing key ({srk_pair_id})." - ) - elif self.certificate and self.certificate.permission_to_sign_container: - # In this case the certificate is signed by the key with given SRK ID - if not public_keys[data["flag_used_srk_id"]].verify_signature( - self.certificate.signature.signature_data, - self.certificate.get_signature_data(), - ): - raise SPSDKValueError( - f"Certificate signature cannot be verified with the key with SRK ID {data['flag_used_srk_id']} " - ) - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary chunk to the container object. - - :param data: Binary data with Signature block to parse. - :return: Object recreated from the binary data. - """ - SignatureBlock.check_container_head(data) - ( - _, # version - _, # container_length - _, # tag - certificate_offset, - srk_table_offset, - signature_offset, - blob_offset, - key_identifier, - ) = unpack(SignatureBlock.format(), data[: SignatureBlock.fixed_length()]) - - signature_block = cls() - signature_block.srk_table = ( - SRKTable.parse(data[srk_table_offset:]) if srk_table_offset else None - ) - signature_block.certificate = ( - Certificate.parse(data[certificate_offset:]) if certificate_offset else None - ) - signature_block.signature = ( - ContainerSignature.parse(data[signature_offset:]) - if signature_offset - else None - ) - try: - signature_block.blob = ( - Blob.parse(data[blob_offset:]) if blob_offset else None - ) - if signature_block.blob: - signature_block.blob.key_identifier = key_identifier - except SPSDKParsingError as exc: - logger.warning( - "AHAB Blob parsing error. In case that no encrypted images" - " are presented in container, it should not be an big issue." - f"\n{str(exc)}" - ) - signature_block.blob = None - - return signature_block - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "SignatureBlock": - """Converts the configuration option into an AHAB Signature block object. - - "config" content of container configurations. - - :param config: array of AHAB signature block configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: AHAB Signature block object. - """ - signature_block = SignatureBlock() - # SRK Table - srk_table_cfg = config.get("srk_table") - signature_block.srk_table = ( - SRKTable.load_from_config(srk_table_cfg, search_paths) - if srk_table_cfg - else None - ) - - # Container Signature - srk_set = config.get("srk_set", "none") - signature_block.signature = ( - ContainerSignature.load_from_config(config, search_paths) - if srk_set != "none" - else None - ) - - # Certificate Block - signature_block.certificate = None - certificate_cfg = config.get("certificate") - - if certificate_cfg: - try: - cert_cfg = load_configuration(certificate_cfg) - check_config( - cert_cfg, - Certificate.get_validation_schemas(), - search_paths=search_paths, - ) - signature_block.certificate = Certificate.load_from_config(cert_cfg) - except SPSDKError: - # this could be pre-exported binary certificate :-) - signature_block.certificate = Certificate.parse( - load_binary(certificate_cfg, search_paths) - ) - - # DEK blob - blob_cfg = config.get("blob") - signature_block.blob = ( - Blob.load_from_config(blob_cfg, search_paths) if blob_cfg else None - ) - - return signature_block - - -class AHABContainerBase(HeaderContainer): - """Class representing AHAB container base class (common for Signed messages and AHAB Image). - - Container header:: - - +---------------+----------------+----------------+----------------+ - | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +---------------+----------------+----------------+----------------+ - | Tag | Length | Version | - +---------------+---------------------------------+----------------+ - | Flags | - +---------------+----------------+---------------------------------+ - | # of images | Fuse version | SW version | - +---------------+----------------+---------------------------------+ - | Reserved | Signature Block Offset | - +--------------------------------+---------------------------------+ - | Payload (Signed Message or Image Array) | - +------------------------------------------------------------------+ - | Signature block | - +------------------------------------------------------------------+ - - """ - - TAG = 0x00 # Need to be updated by child class - VERSION = 0x00 - FLAGS_SRK_SET_OFFSET = 0 - FLAGS_SRK_SET_SIZE = 2 - FLAGS_SRK_SET_VAL = {"none": 0, "nxp": 1, "oem": 2} - FLAGS_USED_SRK_ID_OFFSET = 4 - FLAGS_USED_SRK_ID_SIZE = 2 - FLAGS_SRK_REVOKE_MASK_OFFSET = 8 - FLAGS_SRK_REVOKE_MASK_SIZE = 4 - - def __init__( - self, - flags: int = 0, - fuse_version: int = 0, - sw_version: int = 0, - signature_block: Optional["SignatureBlock"] = None, - ): - """Class object initializer. - - :param flags: flags. - :param fuse_version: value must be equal to or greater than the version - stored in the fuses to allow loading this container. - :param sw_version: used by PHBC (Privileged Host Boot Companion) to select - between multiple images with same fuse version field. - :param signature_block: signature block. - """ - super().__init__(tag=self.TAG, length=-1, version=self.VERSION) - self.flags = flags - self.fuse_version = fuse_version - self.sw_version = sw_version - self.signature_block = signature_block or SignatureBlock() - self.search_paths: List[str] = [] - self.lock = False - - def __eq__(self, other: object) -> bool: - if isinstance(other, AHABContainerBase): - if ( - super().__eq__(other) - and self.flags == other.flags - and self.fuse_version == other.fuse_version - and self.sw_version == other.sw_version - ): - return True - - return False - - def set_flags( - self, srk_set: str = "none", used_srk_id: int = 0, srk_revoke_mask: int = 0 - ) -> None: - """Set the flags value. - - :param srk_set: Super Root Key (SRK) set, defaults to "none" - :param used_srk_id: Which key from SRK set is being used, defaults to 0 - :param srk_revoke_mask: SRK revoke mask, defaults to 0 - """ - flags = self.FLAGS_SRK_SET_VAL[srk_set.lower()] - flags |= used_srk_id << 4 - flags |= srk_revoke_mask << 8 - self.flags = flags - - @property - def flag_srk_set(self) -> str: - """SRK set flag in string representation. - - :return: Name of SRK Set flag. - """ - srk_set = (self.flags >> self.FLAGS_SRK_SET_OFFSET) & ( - (1 << self.FLAGS_SRK_SET_SIZE) - 1 - ) - return get_key_by_val(self.FLAGS_SRK_SET_VAL, srk_set) - - @property - def flag_used_srk_id(self) -> int: - """Used SRK ID flag. - - :return: Index of Used SRK ID. - """ - return (self.flags >> self.FLAGS_USED_SRK_ID_OFFSET) & ( - (1 << self.FLAGS_USED_SRK_ID_SIZE) - 1 - ) - - @property - def flag_srk_revoke_mask(self) -> str: - """SRK Revoke mask flag. - - :return: SRK revoke mask in HEX. - """ - srk_revoke_mask = (self.flags >> self.FLAGS_SRK_REVOKE_MASK_OFFSET) & ( - (1 << self.FLAGS_SRK_REVOKE_MASK_SIZE) - 1 - ) - return hex(srk_revoke_mask) - - @property - def _signature_block_offset(self) -> int: - """Returns current signature block offset. - - :return: Offset in bytes of Signature block. - """ - # Constant size of Container header + Image array Entry table - return align( - super().__len__(), - CONTAINER_ALIGNMENT, - ) - - @property - def image_array_len(self) -> int: - """Get image array length if available. - - :return: Length of image array. - """ - return 0 - - def __len__(self) -> int: - """Get total length of AHAB container. - - :return: Size in bytes of AHAB Container. - """ - # If there are no images just return length of header - return self.header_length() - - def header_length(self) -> int: - """Length of AHAB Container header. - - :return: Length in bytes of AHAB Container header. - """ - return ( - super().__len__() - + len( # This returns the fixed length of the container header - self.signature_block - ) - ) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() - + UINT32 # Flags - + UINT16 # SW version - + UINT8 # Fuse version - + UINT8 # Number of Images - + UINT16 # Signature Block Offset - + UINT16 # Reserved - ) - - def update_fields(self) -> None: - """Updates all volatile information in whole container structure. - - :raises SPSDKError: When inconsistent image array length is detected. - """ - # Update the signature block to get overall size of it - self.signature_block.update_fields() - # Update the Container header length - self.length = self.header_length() - # # Sign the image header - if self.flag_srk_set != "none": - assert self.signature_block.signature - self.signature_block.signature.sign(self.get_signature_data()) - - def get_signature_data(self) -> bytes: - """Returns binary data to be signed. - - The container must be properly initialized, so the data are valid for - signing, i.e. the offsets, lengths etc. must be set prior invoking this - method, otherwise improper data will be signed. - - The whole container gets serialized first. Afterwards the binary data - is sliced so only data for signing get's returned. The signature data - length is evaluated based on offsets, namely the signature block offset, - the container signature offset and the container signature fixed data length. - - Signature data structure:: - - +---------------------------------------------------+----------------+ - | Container header | | - +---+---+-----------+---------+--------+------------+ Data | - | S | | tag | length | length | version | | - | i | +-----------+---------+--------+------------+ | - | g | | flags | to | - | n | +---------------------+---------------------+ | - | a | | srk table offset | certificate offset | | - | t | +---------------------+---------------------+ Sign | - | u | | blob offset | signature offset | | - | r | +---------------------+---------------------+ | - | e | | SRK Table | | - | +---+-----------+---------+--------+------------+----------------+ - | B | S | tag | length | length | version | Signature data | - | l | i +-----------+---------+--------+------------+ fixed length | - | o | g | Reserved | | - | c | n +-------------------------------------------+----------------+ - | k | a | Signature data | - | | t | | - | | u | | - | | r | | - | | e | | - +---+---+-------------------------------------------+ - - :raises SPSDKValueError: if Signature Block or SRK Table is missing. - :return: bytes representing data to be signed. - """ - if not self.signature_block.signature or not self.signature_block.srk_table: - raise SPSDKValueError( - "Can't retrieve data block to sign. Signature or SRK table is missing!" - ) - - signature_offset = ( - self._signature_block_offset + self.signature_block.signature_offset - ) - return self._export()[:signature_offset] - - def _export(self) -> bytes: - """Export container header into bytes. - - :return: bytes representing container header content including the signature block. - """ - return pack( - self.format(), - self.version, - self.length, - self.tag, - self.flags, - self.sw_version, - self.fuse_version, - self.image_array_len, - self._signature_block_offset, - RESERVED, # Reserved field - ) - - def validate(self, data: Dict[str, Any]) -> None: - """Validate object data. - - :param data: Additional validation data. - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - self.validate_header() - - if self.flags is None or not check_range(self.flags, end=(1 << 32) - 1): - raise SPSDKValueError(f"Container Header: Invalid flags: {hex(self.flags)}") - if self.sw_version is None or not check_range( - self.sw_version, end=(1 << 16) - 1 - ): - raise SPSDKValueError( - f"Container Header: Invalid SW version: {hex(self.sw_version)}" - ) - if self.fuse_version is None or not check_range( - self.fuse_version, end=(1 << 8) - 1 - ): - raise SPSDKValueError( - f"Container Header: Invalid Fuse version: {hex(self.fuse_version)}" - ) - self.signature_block.validate(data) - - @staticmethod - def _parse(binary: bytes) -> Tuple[int, int, int, int, int]: - """Parse input binary chunk to the container object. - - :param parent: AHABImage object. - :param binary: Binary data with Container block to parse. - :return: Object recreated from the binary data. - """ - AHABContainer.check_container_head(binary) - image_format = AHABContainer.format() - ( - _, # version - _, # container_length - _, # tag - flags, - sw_version, - fuse_version, - number_of_images, - signature_block_offset, - _, # reserved - ) = unpack(image_format, binary[: AHABContainer.fixed_length()]) - - return ( - flags, - sw_version, - fuse_version, - number_of_images, - signature_block_offset, - ) - - def _create_config(self, index: int, data_path: str) -> Dict[str, Any]: - """Create configuration of the AHAB Image. - - :param index: Container index. - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - cfg: Dict[str, Any] = {} - - cfg["srk_set"] = self.flag_srk_set - cfg["used_srk_id"] = self.flag_used_srk_id - cfg["srk_revoke_mask"] = self.flag_srk_revoke_mask - cfg["fuse_version"] = self.fuse_version - cfg["sw_version"] = self.sw_version - cfg["signing_key"] = "N/A" - - if self.signature_block.srk_table: - cfg["srk_table"] = self.signature_block.srk_table.create_config( - index, data_path - ) - - if self.signature_block.certificate: - cert_cfg = self.signature_block.certificate.create_config( - index, data_path, self.flag_srk_set - ) - write_file( - CommentedConfig( - "Parsed AHAB Certificate", Certificate.get_validation_schemas() - ).get_config(cert_cfg), - os.path.join(data_path, "certificate.yaml"), - ) - cfg["certificate"] = "certificate.yaml" - - if self.signature_block.blob: - cfg["blob"] = self.signature_block.blob.create_config(index, data_path) - - return cfg - - def load_from_config_generic(self, config: Dict[str, Any]) -> None: - """Converts the configuration option into an AHAB image object. - - "config" content of container configurations. - - :param config: array of AHAB containers configuration dictionaries. - """ - self.set_flags( - srk_set=config.get("srk_set", "none"), - used_srk_id=value_to_int(config.get("used_srk_id", 0)), - srk_revoke_mask=value_to_int(config.get("srk_revoke_mask", 0)), - ) - self.fuse_version = value_to_int(config.get("fuse_version", 0)) - self.sw_version = value_to_int(config.get("sw_version", 0)) - - self.signature_block = SignatureBlock.load_from_config( - config, search_paths=self.search_paths - ) - - -class AHABContainer(AHABContainerBase): - """Class representing AHAB container. - - Container header:: - - +---------------+----------------+----------------+----------------+ - | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +---------------+----------------+----------------+----------------+ - | Tag | Length | Version | - +---------------+---------------------------------+----------------+ - | Flags | - +---------------+----------------+---------------------------------+ - | # of images | Fuse version | SW version | - +---------------+----------------+---------------------------------+ - | Reserved | Signature Block Offset | - +----+---------------------------+---------------------------------+ - | I |image0: Offset, Size, LoadAddr, EntryPoint, Flags, Hash, IV | - + m |-------------------------------------------------------------+ - | g |image1: Offset, Size, LoadAddr, EntryPoint, Flags, Hash, IV | - + . |-------------------------------------------------------------+ - | A |... | - | r |... | - | r | | - + a |-------------------------------------------------------------+ - | y |imageN: Offset, Size, LoadAddr, EntryPoint, Flags, Hash, IV | - +----+-------------------------------------------------------------+ - | Signature block | - +------------------------------------------------------------------+ - | | - | | - | | - +------------------------------------------------------------------+ - | Data block_0 | - +------------------------------------------------------------------+ - | | - | | - +------------------------------------------------------------------+ - | Data block_n | - +------------------------------------------------------------------+ - - """ - - TAG = AHABTags.CONTAINER_HEADER.tag - - def __init__( - self, - parent: "AHABImage", - flags: int = 0, - fuse_version: int = 0, - sw_version: int = 0, - image_array: Optional[List["ImageArrayEntry"]] = None, - signature_block: Optional["SignatureBlock"] = None, - container_offset: int = 0, - ): - """Class object initializer. - - :parent: Parent AHABImage object. - :param flags: flags. - :param fuse_version: value must be equal to or greater than the version - stored in the fuses to allow loading this container. - :param sw_version: used by PHBC (Privileged Host Boot Companion) to select - between multiple images with same fuse version field. - :param image_array: array of image entries, must be `number of images` long. - :param signature_block: signature block. - """ - super().__init__( - flags=flags, - fuse_version=fuse_version, - sw_version=sw_version, - signature_block=signature_block, - ) - self.parent = parent - assert self.parent is not None - self.image_array = image_array or [] - self.container_offset = container_offset - self.search_paths: List[str] = [] - - def __eq__(self, other: object) -> bool: - if isinstance(other, AHABContainer): - if super().__eq__(other) and self.image_array == other.image_array: - return True - - return False - - def __repr__(self) -> str: - return f"AHAB Container at offset {hex(self.container_offset)} " - - def __str__(self) -> str: - return ( - "AHAB Container:\n" - f" Index: {'0' if self.container_offset == 0 else '1'}\n" - f" Flags: {hex(self.flags)}\n" - f" Fuse version: {hex(self.fuse_version)}\n" - f" SW version: {hex(self.sw_version)}\n" - f" Images count: {self.image_array_len}" - ) - - @property - def image_array_len(self) -> int: - """Get image array length if available. - - :return: Length of image array. - """ - return len(self.image_array) - - @property - def _signature_block_offset(self) -> int: - """Returns current signature block offset. - - :return: Offset in bytes of Signature block. - """ - # Constant size of Container header + Image array Entry table - return align( - super().fixed_length() - + len(self.image_array) * ImageArrayEntry.fixed_length(), - CONTAINER_ALIGNMENT, - ) - - def __len__(self) -> int: - """Get total length of AHAB container. - - :return: Size in bytes of AHAB Container. - """ - # Get image which has biggest offset - possible_sizes = [self.header_length()] - possible_sizes.extend( - [align(x.image_offset + x.image_size) for x in self.image_array] - ) - - return align(max(possible_sizes), CONTAINER_ALIGNMENT) - - def header_length(self) -> int: - """Length of AHAB Container header. - - :return: Length in bytes of AHAB Container header. - """ - return ( - super().fixed_length() # This returns the fixed length of the container header - # This returns the total length of all image array entries - + len(self.image_array) * ImageArrayEntry.fixed_length() - # This returns the length of signature block (including SRK table, - # blob etc. if present) - + len(self.signature_block) - ) - - def update_fields(self) -> None: - """Updates all volatile information in whole container structure. - - :raises SPSDKError: When inconsistent image array length is detected. - """ - # 1. Encrypt all images if applicable - for image_entry in self.image_array: - if ( - image_entry.flags_is_encrypted - and not image_entry.already_encrypted_image - and self.signature_block.blob - ): - image_entry.encrypted_image = self.signature_block.blob.encrypt_data( - image_entry.image_iv[16:], image_entry.plain_image - ) - image_entry.already_encrypted_image = True - - # 2. Update the signature block to get overall size of it - self.signature_block.update_fields() - # 3. Updates Image Entries - for image_entry in self.image_array: - image_entry.update_fields() - # 4. Update the Container header length - self.length = self.header_length() - # 5. Sign the image header - if self.flag_srk_set != "none": - assert self.signature_block.signature - self.signature_block.signature.sign(self.get_signature_data()) - - def decrypt_data(self) -> None: - """Decrypt all images if possible.""" - for i, image_entry in enumerate(self.image_array): - if image_entry.flags_is_encrypted: - if self.signature_block.blob is None: - raise SPSDKError("Cannot decrypt image without Blob!") - - decrypted_data = self.signature_block.blob.decrypt_data( - image_entry.image_iv[16:], image_entry.encrypted_image - ) - if image_entry.image_iv == get_hash( - decrypted_data, algorithm=EnumHashAlgorithm.SHA256 - ): - image_entry.plain_image = decrypted_data - logger.info( - f" Image{i} from AHAB container at offset {hex(self.container_offset)} has been decrypted." - ) - else: - logger.warning( - f" Image{i} from AHAB container at offset {hex(self.container_offset)} decryption failed." - ) - - def _export(self) -> bytes: - """Export container header into bytes. - - :return: bytes representing container header content including the signature block. - """ - return self.export() - - def export(self) -> bytes: - """Export container header into bytes. - - :return: bytes representing container header content including the signature block. - """ - container_header = bytearray(align(self.header_length(), CONTAINER_ALIGNMENT)) - container_header_only = super()._export() - - for image_array_entry in self.image_array: - container_header_only += image_array_entry.export() - - container_header[: self._signature_block_offset] = container_header_only - # Add Signature Block - container_header[ - self._signature_block_offset : self._signature_block_offset - + align(len(self.signature_block), CONTAINER_ALIGNMENT) - ] = self.signature_block.export() - - return container_header - - def validate(self, data: Dict[str, Any]) -> None: - """Validate object data. - - :param data: Additional validation data. - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - data["flag_used_srk_id"] = self.flag_used_srk_id - self.validate_header() - if self.length != self.header_length(): - raise SPSDKValueError( - f"Container 0x{self.container_offset:04X} " - f"Header: Invalid block length: {self.length} != {self.header_length()}" - ) - - super().validate(data) - - if self.image_array is None or len(self.image_array) == 0: - raise SPSDKValueError( - f"Container 0x{self.container_offset:04X} Header: Invalid Image Array: {self.image_array}" - ) - - for container, offset in zip( - self.parent.ahab_containers, self.parent.ahab_address_map - ): - if self == container: - if self.container_offset != offset: - raise SPSDKValueError( - f"AHAB Container 0x{self.container_offset:04X}: Invalid Container Offset." - ) - - if self.signature_block.srk_table and self.signature_block.signature: - # Get public key with the SRK ID - key = self.signature_block.srk_table.get_source_keys()[ - self.flag_used_srk_id - ] - if self.signature_block.certificate: - # Verify signature of certificate - if not key.verify_signature( - self.signature_block.certificate.signature.signature_data, - self.signature_block.certificate.get_signature_data(), - ): - raise SPSDKValueError( - f"AHAB Container 0x{self.container_offset:04X}: Certificate block signature " - f"cannot be verified with SRK ID {self.flag_used_srk_id}" - ) - - if ( - self.signature_block.certificate - and self.signature_block.certificate.permission_to_sign_container - ): - # Container is signed by certificate, get public key from certificate - assert ( - self.signature_block.certificate.public_key - ), "Certificate must contain public key" - key = PublicKey.parse( - self.signature_block.certificate.public_key.get_public_key() - ) - - if not key.verify_signature( - self.signature_block.signature.signature_data, self.get_signature_data() - ): - if ( - self.signature_block.certificate - and self.signature_block.certificate.permission_to_sign_container - ): - raise SPSDKValueError( - f"AHAB Container 0x{self.container_offset:04X}: " - "Signature cannot be verified with the public key from certificate" - ) - raise SPSDKValueError( - f"AHAB Container 0x{self.container_offset:04X}: " - f"Signature cannot be verified with SRK ID {self.flag_used_srk_id}" - ) - - for image in self.image_array: - image.validate() - - @classmethod - def parse(cls, data: bytes, parent: "AHABImage", container_id: int) -> Self: # type: ignore# type: ignore # pylint: disable=arguments-differ - """Parse input binary chunk to the container object. - - :param data: Binary data with Container block to parse. - :param parent: AHABImage object. - :param container_id: AHAB container ID. - :return: Object recreated from the binary data. - """ - if parent is None: - raise SPSDKValueError("Ahab Image must be specified.") - ( - flags, - sw_version, - fuse_version, - number_of_images, - signature_block_offset, - ) = AHABContainerBase._parse(data) - - parsed_container = cls( - parent=parent, - flags=flags, - fuse_version=fuse_version, - sw_version=sw_version, - container_offset=parent.ahab_address_map[container_id], - ) - parsed_container.signature_block = SignatureBlock.parse( - data[signature_block_offset:] - ) - - for i in range(number_of_images): - image_array_entry = ImageArrayEntry.parse( - data[ - AHABContainer.fixed_length() + i * ImageArrayEntry.fixed_length() : - ], - parsed_container, - ) - parsed_container.image_array.append(image_array_entry) - # Lock the parsed container to any updates of offsets - parsed_container.lock = True - return parsed_container - - def create_config(self, index: int, data_path: str) -> Dict[str, Any]: - """Create configuration of the AHAB Image. - - :param index: Container index. - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - ret_cfg = {} - cfg = self._create_config(index, data_path) - images_cfg = [] - - for img_ix, image in enumerate(self.image_array): - images_cfg.append(image.create_config(index, img_ix, data_path)) - cfg["images"] = images_cfg - - ret_cfg["container"] = cfg - return ret_cfg - - @staticmethod - def load_from_config( - parent: "AHABImage", config: Dict[str, Any], container_ix: int - ) -> "AHABContainer": - """Converts the configuration option into an AHAB image object. - - "config" content of container configurations. - - :param parent: AHABImage object. - :param config: array of AHAB containers configuration dictionaries. - :param container_ix: Container index that is loaded. - :return: AHAB Container object. - """ - ahab_container = AHABContainer(parent) - ahab_container.search_paths = parent.search_paths or [] - ahab_container.container_offset = parent.ahab_address_map[container_ix] - ahab_container.load_from_config_generic(config) - images = config.get("images") - assert isinstance(images, list) - for image in images: - ahab_container.image_array.append( - ImageArrayEntry.load_from_config(ahab_container, image) - ) - - return ahab_container - - def image_info(self) -> BinaryImage: - """Get Image info object. - - :return: AHAB Container Info object. - """ - ret = BinaryImage( - name="AHAB Container", - size=self.header_length(), - offset=0, - binary=self.export(), - description=( - f"AHAB Container for {self.flag_srk_set}" f"_SWver:{self.sw_version}" - ), - ) - return ret - - -class AHABImage: - """Class representing an AHAB image. - - The image consists of multiple AHAB containers. - """ - - TARGET_MEMORIES = [ - TARGET_MEMORY_SERIAL_DOWNLOADER, - TARGET_MEMORY_NOR, - TARGET_MEMORY_NAND_4K, - TARGET_MEMORY_NAND_2K, - ] - - def __init__( - self, - family: str, - revision: str = "latest", - target_memory: str = TARGET_MEMORY_NOR, - ahab_containers: Optional[List[AHABContainer]] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """AHAB Image constructor. - - :param family: Name of device family. - :param revision: Device silicon revision, defaults to "latest" - :param target_memory: Target memory for AHAB image [serial_downloader, nor, nand], defaults to "nor" - :param ahab_containers: _description_, defaults to None - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKValueError: Invalid input configuration. - """ - if target_memory not in self.TARGET_MEMORIES: - raise SPSDKValueError( - f"Invalid AHAB target memory [{target_memory}]." - f" The list of supported images: [{','.join(self.TARGET_MEMORIES)}]" - ) - self.target_memory = target_memory - self.family = family - self.search_paths = search_paths - self._database = get_db(family, revision) - self.revision = self._database.name - self.ahab_address_map: List[int] = self._database.get_list( - DatabaseManager.AHAB, "ahab_map" - ) - self.start_image_address = ( - START_IMAGE_ADDRESS_NAND - if target_memory in [TARGET_MEMORY_NAND_2K, TARGET_MEMORY_NAND_4K] - else START_IMAGE_ADDRESS - ) - self.containers_max_cnt = self._database.get_int( - DatabaseManager.AHAB, "containers_max_cnt" - ) - self.images_max_cnt = self._database.get_int( - DatabaseManager.AHAB, "oem_images_max_cnt" - ) - self.srkh_sha_supports: List[str] = self._database.get_list( - DatabaseManager.AHAB, "srkh_sha_supports" - ) - self.ahab_containers: List[AHABContainer] = ahab_containers or [] - - def __repr__(self) -> str: - return f"AHAB Image for {self.family}" - - def __str__(self) -> str: - return ( - "AHAB Image:\n" - f" Family: {self.family}\n" - f" Revision: {self.revision}\n" - f" Target memory: {self.target_memory}\n" - f" Max cont. count: {self.containers_max_cnt}" - f" Max image. count: {self.images_max_cnt}" - f" Containers count: {len(self.ahab_containers)}" - ) - - def add_container(self, container: AHABContainer) -> None: - """Add new container into AHAB Image. - - The order of the added images is important. - :param container: New AHAB Container to be added. - :raises SPSDKLengthError: The container count in image is overflowed. - """ - if len(self.ahab_containers) >= self.containers_max_cnt: - raise SPSDKLengthError( - "Cannot add new container because the AHAB Image already reached" - f" the maximum count: {self.containers_max_cnt}" - ) - - self.ahab_containers.append(container) - - def clear(self) -> None: - """Clear list of containers.""" - self.ahab_containers.clear() - - def update_fields(self, update_offsets: bool = True) -> None: - """Automatically updates all volatile fields in every AHAB container. - - :param update_offsets: Update also offsets for serial_downloader. - """ - for ahab_container in self.ahab_containers: - ahab_container.update_fields() - - if self.target_memory == TARGET_MEMORY_SERIAL_DOWNLOADER and update_offsets: - # Update the Image offsets to be without gaps - offset = self.start_image_address - for ahab_container in self.ahab_containers: - for image in ahab_container.image_array: - if ahab_container.lock: - offset = image.image_offset - else: - image.image_offset = offset - offset = image.get_valid_offset(offset + image.image_size) - - ahab_container.update_fields() - - def __len__(self) -> int: - """Get maximal size of AHAB Image. - - :return: Size in Bytes of AHAB Image. - """ - lengths = [0] - for container in self.ahab_containers: - lengths.append(len(container)) - return align(max(lengths), CONTAINER_ALIGNMENT) - - def get_containers_size(self) -> int: - """Get maximal containers size. - - In fact get the offset where could be stored first data. - - :return: Size of containers. - """ - if len(self.ahab_containers) == 0: - return 0 - sizes = [ - container.header_length() + address - for container, address in zip(self.ahab_containers, self.ahab_address_map) - ] - return align(max(sizes), CONTAINER_ALIGNMENT) - - def get_first_data_image_address(self) -> int: - """Get first data image address. - - :return: Address of first data image. - """ - addresses = [] - for container in self.ahab_containers: - addresses.extend([x.image_offset for x in container.image_array]) - return min(addresses) - - def export(self) -> bytes: - """Export AHAB Image. - - :raises SPSDKValueError: mismatch between number of containers and offsets. - :raises SPSDKValueError: number of images mismatch. - :return: bytes AHAB Image. - """ - self.update_fields() - self.validate() - return self.image_info().export() - - def image_info(self) -> BinaryImage: - """Get Image info object.""" - ret = BinaryImage( - name="AHAB Image", - size=len(self), - offset=0, - description=f"AHAB Image for {self.family}_{self.revision}", - pattern=BinaryPattern("0xCA"), - ) - ahab_containers = BinaryImage( - name="AHAB Containers", - size=self.start_image_address, - offset=0, - description="AHAB Containers block", - pattern=BinaryPattern("zeros"), - ) - ret.add_image(ahab_containers) - - for cnt_ix, (container, address) in enumerate( - zip(self.ahab_containers, self.ahab_address_map) - ): - container_image = container.image_info() - container_image.name = container_image.name + f" {cnt_ix}" - container_image.offset = address - ahab_containers.add_image(container_image) - - # Add also all data images - for img_ix, image_entry in enumerate(container.image_array): - data_image = BinaryImage( - name=f"Container {cnt_ix} AHAB Data Image {img_ix}", - binary=image_entry.image, - size=image_entry.image_size, - offset=image_entry.image_offset, - description=( - f"AHAB {'encrypted ' if image_entry.flags_is_encrypted else ''}" - f"data block with {image_entry.flags_image_type} Image Type." - ), - ) - - ret.add_image(data_image) - - return ret - - def validate(self) -> None: - """Validate object data. - - :raises SPSDKValueError: Invalid any value of Image Array entry. - :raises SPSDKError: In case of Binary Image validation fail. - """ - if self.ahab_containers is None or len(self.ahab_containers) == 0: - raise SPSDKValueError("AHAB Image: Missing Containers.") - if len(self.ahab_containers) > self.containers_max_cnt: - raise SPSDKValueError( - "AHAB Image: Too much AHAB containers in image." - f" {len(self.ahab_containers)} > {self.containers_max_cnt}" - ) - # prepare additional validation data - data = {} - data["srkh_sha_supports"] = self.srkh_sha_supports - - for cnt_ix, container in enumerate(self.ahab_containers): - container.validate(data) - if len(container.image_array) > self.images_max_cnt: - raise SPSDKValueError( - f"AHAB Image: Too many binary images in AHAB Container [{cnt_ix}]." - f" {len(container.image_array)} > {self.images_max_cnt}" - ) - if self.target_memory != TARGET_MEMORY_SERIAL_DOWNLOADER: - for img_ix, image_entry in enumerate(container.image_array): - if image_entry.image_offset_real < self.start_image_address: - raise SPSDKValueError( - "AHAB Data Image: The offset of data image (container" - f"{cnt_ix}/image{img_ix}) is under minimal allowed value." - f" 0x{hex(image_entry.image_offset_real)} < {hex(self.start_image_address)}" - ) - - # Validate correct data image offsets - offset = self.start_image_address - alignment = self.ahab_containers[0].image_array[0].get_valid_alignment() - for container in self.ahab_containers: - for image in container.image_array: - if image.image_offset_real != align(image.image_offset_real, alignment): - raise SPSDKValueError( - f"Image Entry: Invalid Image Offset alignment for target memory '{self.target_memory}': " - f"{hex(image.image_offset_real)} " - f"should be with alignment {hex(alignment)}.\n" - f"For example: Bootable image offset ({hex(TARGET_MEMORY_BOOT_OFFSETS[self.target_memory])})" - " + offset (" - f"{hex(align(image.image_offset, alignment) - TARGET_MEMORY_BOOT_OFFSETS[self.target_memory])})" - " is correctly aligned." - ) - if self.target_memory == TARGET_MEMORY_SERIAL_DOWNLOADER: - if offset != image.image_offset and not container.lock: - raise SPSDKValueError( - "Invalid image offset for Serial Downloader mode." - f"\n Expected {hex(offset)}, Used:{hex(image.image_offset_real)}" - ) - else: - offset = image.image_offset - offset = image.get_valid_offset(offset + image.image_size) - alignment = image.get_valid_alignment() - - # Validate also overlapped images - try: - self.image_info().validate() - except SPSDKError as exc: - logger.error(self.image_info().draw()) - raise SPSDKError("Validation failed") from exc - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "AHABImage": - """Converts the configuration option into an AHAB image object. - - "config" content array of containers configurations. - - :param config: array of AHAB containers configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKValueError: if the count of AHAB containers is invalid. - :raises SPSDKParsingError: Cannot parse input binary AHAB container. - :return: Initialized AHAB Image. - """ - containers_config: List[Dict[str, Any]] = config["containers"] - family = config["family"] - revision = config.get("revision", "latest") - target_memory = config.get("target_memory") - if target_memory is None: - # backward compatible reading of obsolete image type - image_type = config["image_type"] - target_memory = { - "xip": "nor", - "non_xip": "nor", - "nand": "nand_2k", - "serial_downloader": "serial_downloader", - }[image_type] - logger.warning( - f"The obsolete key 'image_type':{image_type} has been converted into 'target_memory':{target_memory}" - ) - ahab = AHABImage( - family=family, - revision=revision, - target_memory=target_memory, - search_paths=search_paths, - ) - i = 0 - for container_config in containers_config: - binary_container = container_config.get("binary_container") - if binary_container: - assert isinstance(binary_container, dict) - path = binary_container.get("path") - assert path - ahab_bin = load_binary(path, search_paths=search_paths) - for j in range(ahab.containers_max_cnt): - try: - ahab.add_container( - AHABContainer.parse( - ahab_bin[ahab.ahab_address_map[j] :], - parent=ahab, - container_id=i, - ) - ) - i += 1 - except SPSDKError as exc: - if j == 0: - raise SPSDKParsingError( - f"AHAB Binary Container parsing failed. ({str(exc)})" - ) from exc - else: - break - - else: - ahab.add_container( - AHABContainer.load_from_config( - ahab, container_config["container"], i - ) - ) - i += 1 - - return ahab - - def parse(self, binary: bytes) -> None: - """Parse input binary chunk to the container object. - - :raises SPSDKError: No AHAB container found in binary data. - """ - self.clear() - - for i, address in enumerate(self.ahab_address_map): - try: - container = AHABContainer.parse( - binary[address:], parent=self, container_id=i - ) - self.ahab_containers.append(container) - except SPSDKParsingError as exc: - logger.debug(f"AHAB Image parsing error:\n{str(exc)}") - except SPSDKError as exc: - raise SPSDKError(f"AHAB Container parsing failed: {str(exc)}.") from exc - if len(self.ahab_containers) == 0: - raise SPSDKError("No AHAB Container has been found in binary data.") - - @staticmethod - def get_supported_families() -> List[str]: - """Get all supported families for AHAB container. - - :return: List of supported families. - """ - return get_families(DatabaseManager.AHAB) - - @staticmethod - def get_validation_schemas() -> List[Dict[str, Any]]: - """Get list of validation schemas. - - :return: Validation list of schemas. - """ - sch = DatabaseManager().db.get_schema_file(DatabaseManager.AHAB)[ - "whole_ahab_image" - ] - sch["properties"]["family"]["enum"] = AHABImage.get_supported_families() - return [sch] - - @staticmethod - def generate_config_template(family: str) -> Dict[str, Any]: - """Generate AHAB configuration template. - - :param family: Family for which the template should be generated. - :return: Dictionary of individual templates (key is name of template, value is template itself). - """ - val_schemas = AHABImage.get_validation_schemas() - val_schemas[0]["properties"]["family"]["template_value"] = family - - yaml_data = CommentedConfig( - f"Advanced High-Assurance Boot Configuration template for {family}.", - val_schemas, - ).get_template() - - return {f"{family}_ahab": yaml_data} - - def create_config(self, data_path: str) -> Dict[str, Any]: - """Create configuration of the AHAB Image. - - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - cfg: Dict[str, Any] = {} - cfg["family"] = self.family - cfg["revision"] = self.revision - cfg["target_memory"] = self.target_memory - cfg["output"] = "N/A" - cfg_containers = [] - for cnt_ix, container in enumerate(self.ahab_containers): - cfg_containers.append(container.create_config(cnt_ix, data_path)) - cfg["containers"] = cfg_containers - - return cfg - - def create_srk_hash_blhost_script(self, container_ix: int = 0) -> str: - """Create BLHOST script to load SRK hash into fuses. - - :param container_ix: Container index. - :raises SPSDKValueError: Invalid input value - Non existing container or unsupported type. - :raises SPSDKError: Invalid SRK hash. - :return: Script used by BLHOST to load SRK hash. - """ - if container_ix > len(self.ahab_containers): - raise SPSDKValueError(f"Invalid Container index: {container_ix}.") - container_type = self.ahab_containers[container_ix].flag_srk_set - - fuses_start = self._database.get_int( - DatabaseManager.AHAB, f"{container_type}_srkh_fuses_start" - ) - fuses_count = self._database.get_int( - DatabaseManager.AHAB, f"{container_type}_srkh_fuses_count" - ) - fuses_size = self._database.get_int( - DatabaseManager.AHAB, f"{container_type}_srkh_fuses_size" - ) - if fuses_start is None or fuses_count is None or fuses_size is None: - raise SPSDKValueError( - f"Unsupported container type({container_type}) to create BLHOST script" - ) - - srk_table = self.ahab_containers[container_ix].signature_block.srk_table - if srk_table is None: - raise SPSDKError("The selected AHAB container doesn't contain SRK table.") - - srkh = srk_table.compute_srk_hash() - - if len(srkh) != fuses_count * fuses_size: - raise SPSDKError( - f"The SRK hash length ({len(srkh)}) doesn't fit to fuses space ({fuses_count*fuses_size})." - ) - ret = ( - "# BLHOST SRK Hash fuses programming script\n" - f"# Generated by SPSDK {spsdk_version}\n" - f"# Chip: {self.family} rev:{self.revision}\n" - f"# SRK Hash(Big Endian): {srkh.hex()}\n\n" - ) - srkh_rev = reverse_bytes_in_longs(srkh) - for fuse_ix in range(fuses_count): - value = srkh_rev[fuse_ix * 4 : fuse_ix * 4 + 4] - ret += f"# OEM SRKH{fuses_count-1-fuse_ix} fuses.\n" - ret += f"efuse-program-once {hex(fuses_start+fuse_ix)} 0x{value.hex()}\n" - - return ret diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/signed_msg.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/signed_msg.py deleted file mode 100644 index 9c33910b..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/signed_msg.py +++ /dev/null @@ -1,1623 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2021-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Implementation of raw AHAB container support. - -This module represents a generic AHAB container implementation. You can set the -containers values at will. From this perspective, consult with your reference -manual of your device for allowed values. -""" -import datetime -import logging -from abc import abstractmethod -from inspect import isclass -from struct import calcsize, pack, unpack -from typing import Any, Dict, List, Optional, Tuple, Type - -from typing_extensions import Self - -from ...exceptions import SPSDKError, SPSDKValueError -from ...image.ahab.ahab_abstract_interfaces import LITTLE_ENDIAN, Container -from ...image.ahab.ahab_container import ( - CONTAINER_ALIGNMENT, - RESERVED, - UINT8, - UINT16, - UINT32, - AHABContainerBase, - AHABImage, - SignatureBlock, -) -from ...utils.database import DatabaseManager -from ...utils.images import BinaryImage -from ...utils.misc import ( - Endianness, - align_block, - check_range, - load_hex_string, - value_to_int, -) -from ...utils.schema_validator import CommentedConfig -from ...utils.spsdk_enum import SpsdkEnum - -logger = logging.getLogger(__name__) - - -class SignedMessageTags(SpsdkEnum): - """Signed message container related tags.""" - - SIGNED_MSG = (0x89, "SIGNED_MSG", "Signed message.") - - -class MessageCommands(SpsdkEnum): - """Signed messages commands.""" - - KEYSTORE_REPROVISIONING_ENABLE_REQ = ( - 0x3F, - "KEYSTORE_REPROVISIONING_ENABLE_REQ", - "Key store reprovisioning enable", - ) - - KEY_EXCHANGE_REQ = ( - 0x47, - "KEY_EXCHANGE_REQ", - "Key exchange signed message content", - ) - - RETURN_LIFECYCLE_UPDATE_REQ = ( - 0xA0, - "RETURN_LIFECYCLE_UPDATE_REQ", - "Return lifecycle update request.", - ) - WRITE_SEC_FUSE_REQ = (0x91, "WRITE_SEC_FUSE_REQ", "Write secure fuse request.") - - -class Message(Container): - """Class representing the Signed message. - - Message:: - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Message header | - +-----+---------------------------------------------------------------+ - |0x10 | Message payload | - +-----+---------------------------------------------------------------+ - - - Message header:: - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Cert version | Permission | Issue date | - +-----+--------------+--------------+---------------------------------+ - |0x04 | Reserved | Command | Reserved | - +-----+--------------+--------------+---------------------------------+ - |0x08 | Unique ID (Lower 32 bits) | - +-----+---------------------------------------------------------------+ - |0x0c | Unique ID (Upper 32 bits) | - +-----+---------------------------------------------------------------+ - - The message header is common for all signed messages. - - """ - - UNIQUE_ID_LEN = 8 - TAG = 0 - PAYLOAD_LENGTH = 0 - - def __init__( - self, - cert_ver: int = 0, - permissions: int = 0, - issue_date: Optional[int] = None, - cmd: int = 0, - unique_id: Optional[bytes] = None, - ) -> None: - """Message used to sign and send to device with EdgeLock. - - :param cert_ver: Certificate version, defaults to 0 - :param permissions: Certificate permission, to be used in future - The stated permission must allow the operation requested by the signed message - , defaults to 0 - :param issue_date: Issue date, defaults to None (Current date will be applied) - :param cmd: Message command ID, defaults to 0 - :param unique_id: UUID of device (least 64 bits is used), defaults to None - """ - self.cert_ver = cert_ver - self.permissions = permissions - now = datetime.datetime.now() - self.issue_date = issue_date or (now.month << 12 | now.year) - self.cmd = cmd - self.unique_id = unique_id or b"" - - def __repr__(self) -> str: - return f"Message, {MessageCommands.get_description(self.TAG, 'Base Class')}" - - def __str__(self) -> str: - ret = repr(self) + ":\n" - ret += ( - f" Certificate version:{self.cert_ver}\n" - f" Permissions: {hex(self.permissions)}\n" - f" Issue date: {hex(self.issue_date)}\n" - f" UUID: {self.unique_id.hex() if self.unique_id else 'Not Available'}" - ) - return ret - - def __len__(self) -> int: - """Returns the total length of a container. - - The length includes the fixed as well as the variable length part. - """ - return self.fixed_length() + self.payload_len - - @property - def payload_len(self) -> int: - """Message payload length in bytes.""" - return self.PAYLOAD_LENGTH - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() - + UINT16 # Issue Date - + UINT8 # Permission - + UINT8 # Certificate version - + UINT16 # Reserved to zero - + UINT8 # Command - + UINT8 # Reserved - + "4s" # Unique ID (Lower 32 bits) - + "4s" # Unique ID (Upper 32 bits) - ) - - def validate(self) -> None: - """Validate general message properties.""" - if self.cert_ver is None or not check_range(self.cert_ver, end=(1 << 8) - 1): - raise SPSDKValueError( - f"Message: Invalid certificate version: {hex(self.cert_ver) if self.cert_ver else 'None'}" - ) - - if self.permissions is None or not check_range( - self.permissions, end=(1 << 8) - 1 - ): - raise SPSDKValueError( - f"Message: Invalid certificate permission: {hex(self.permissions) if self.permissions else 'None'}" - ) - - if self.issue_date is None or not check_range( - self.issue_date, start=1, end=(1 << 16) - 1 - ): - raise SPSDKValueError( - f"Message: Invalid issue date: {hex(self.issue_date) if self.issue_date else 'None'}" - ) - - if self.cmd is None or self.cmd not in MessageCommands.tags(): - raise SPSDKValueError( - f"Message: Invalid command: {hex(self.cmd) if self.cmd else 'None'}" - ) - - if self.unique_id is None or len(self.unique_id) < Message.UNIQUE_ID_LEN: - raise SPSDKValueError( - f"Message: Invalid unique ID: {self.unique_id.hex() if self.unique_id else 'None'}" - ) - - def export(self) -> bytes: - """Exports message into to bytes array. - - :return: Bytes representation of message object. - """ - msg = pack( - self.format(), - self.issue_date, - self.permissions, - self.cert_ver, - RESERVED, - self.cmd, - RESERVED, - self.unique_id[:4], - self.unique_id[4:8], - ) - msg += self.export_payload() - return msg - - @abstractmethod - def export_payload(self) -> bytes: - """Exports message payload to bytes array. - - :return: Bytes representation of message payload. - """ - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Message": - """Converts the configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: Message object. - """ - command = config.get("command") - assert command and len(command) == 1 - msg_cls = Message.get_message_class(list(command.keys())[0]) - return msg_cls.load_from_config(config, search_paths=search_paths) - - @staticmethod - def load_from_config_generic( - config: Dict[str, Any] - ) -> Tuple[int, int, Optional[int], bytes]: - """Converts the general configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :return: Message object. - """ - cert_ver = value_to_int(config.get("cert_version", 0)) - permission = value_to_int(config.get("cert_permission", 0)) - issue_date_raw = config.get("issue_date", None) - if issue_date_raw: - assert isinstance(issue_date_raw, str) - year, month = issue_date_raw.split("-") - issue_date = max(min(12, int(month)), 1) << 12 | int(year) - else: - issue_date = None - - uuid = bytes.fromhex(config.get("uuid", bytes(Message.UNIQUE_ID_LEN).hex())) - return (cert_ver, permission, issue_date, uuid) - - def _create_general_config(self) -> Dict[str, Any]: - """Create configuration of the general parts of Message. - - :return: Configuration dictionary. - """ - assert self.unique_id - cfg: Dict[str, Any] = {} - cfg["cert_version"] = self.cert_ver - cfg["cert_permission"] = self.permissions - cfg["issue_date"] = f"{(self.issue_date & 0xfff)}-{(self.issue_date>>12) & 0xf}" - cfg["uuid"] = self.unique_id.hex() - - return cfg - - @abstractmethod - def create_config(self) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :return: Configuration dictionary. - """ - - @classmethod - def get_message_class(cls, cmd: str) -> Type[Self]: - """Get the dedicated message class for command.""" - for var in globals(): - obj = globals()[var] - if isclass(obj) and issubclass(obj, Message) and obj is not Message: - assert issubclass(obj, Message) - if MessageCommands.from_label(cmd) == obj.TAG: - return obj # type: ignore - - raise SPSDKValueError(f"Command {cmd} is not supported.") - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary to the signed message object. - - :param data: Binary data with Container block to parse. - :return: Object recreated from the binary data. - """ - ( - issue_date, # issue Date - permission, # permission - certificate_version, # certificate version - _, # Reserved to zero - command, # Command - _, # Reserved - uuid_lower, # Unique ID (Lower 32 bits) - uuid_upper, # Unique ID (Upper 32 bits) - ) = unpack(Message.format(), data[: Message.fixed_length()]) - - cmd_name = MessageCommands.get_label(command) - msg_cls = Message.get_message_class(cmd_name) - parsed_msg = msg_cls( - cert_ver=certificate_version, - permissions=permission, - issue_date=issue_date, - unique_id=uuid_lower + uuid_upper, - ) - parsed_msg.parse_payload(data[Message.fixed_length() :]) - return parsed_msg # type: ignore - - @abstractmethod - def parse_payload(self, data: bytes) -> None: - """Parse payload. - - :param data: Binary data with Payload to parse. - """ - - -class MessageReturnLifeCycle(Message): - """Return life cycle request message class representation.""" - - TAG = MessageCommands.RETURN_LIFECYCLE_UPDATE_REQ.tag - PAYLOAD_LENGTH = 4 - - def __init__( - self, - cert_ver: int = 0, - permissions: int = 0, - issue_date: Optional[int] = None, - unique_id: Optional[bytes] = None, - life_cycle: int = 0, - ) -> None: - """Message used to sign and send to device with EdgeLock. - - :param cert_ver: Certificate version, defaults to 0 - :param permissions: Certificate permission, to be used in future - The stated permission must allow the operation requested by the signed message - , defaults to 0 - :param issue_date: Issue date, defaults to None (Current date will be applied) - :param unique_id: UUID of device (least 64 bits is used), defaults to None - :param life_cycle: Requested life cycle, defaults to 0 - """ - super().__init__( - cert_ver=cert_ver, - permissions=permissions, - issue_date=issue_date, - cmd=self.TAG, - unique_id=unique_id, - ) - self.life_cycle = life_cycle - - def __str__(self) -> str: - ret = super().__str__() - ret += f" Life Cycle: {hex(self.life_cycle)}" - return ret - - def export_payload(self) -> bytes: - """Exports message payload to bytes array. - - :return: Bytes representation of message payload. - """ - return self.life_cycle.to_bytes(length=4, byteorder=Endianness.LITTLE.value) - - def parse_payload(self, data: bytes) -> None: - """Parse payload. - - :param data: Binary data with Payload to parse. - """ - self.life_cycle = int.from_bytes(data[:4], byteorder=Endianness.LITTLE.value) - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Message": - """Converts the configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKError: Invalid configuration detected. - :return: Message object. - """ - command = config.get("command", {}) - if not isinstance(command, dict) or len(command) != 1: - raise SPSDKError(f"Invalid config field command: {command}") - command_name = list(command.keys())[0] - if MessageCommands.from_label(command_name) != MessageReturnLifeCycle.TAG: - raise SPSDKError( - "Invalid configuration for Return Life Cycle Request command." - ) - - cert_ver, permission, issue_date, uuid = Message.load_from_config_generic( - config - ) - - life_cycle = command.get("RETURN_LIFECYCLE_UPDATE_REQ") - assert isinstance(life_cycle, int) - - return MessageReturnLifeCycle( - cert_ver=cert_ver, - permissions=permission, - issue_date=issue_date, - unique_id=uuid, - life_cycle=life_cycle, - ) - - def create_config(self) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :return: Configuration dictionary. - """ - cfg = self._create_general_config() - cmd_cfg = {} - cmd_cfg[MessageCommands.get_label(self.TAG)] = self.life_cycle - cfg["command"] = cmd_cfg - - return cfg - - def validate(self) -> None: - """Validate general message properties.""" - super().validate() - if self.life_cycle is None: - raise SPSDKValueError( - "Message Return Life Cycle request: Invalid life cycle" - ) - - -class MessageWriteSecureFuse(Message): - """Write secure fuse request message class representation.""" - - TAG = MessageCommands.WRITE_SEC_FUSE_REQ.tag - PAYLOAD_FORMAT = LITTLE_ENDIAN + UINT16 + UINT8 + UINT8 - - def __init__( - self, - cert_ver: int = 0, - permissions: int = 0, - issue_date: Optional[int] = None, - unique_id: Optional[bytes] = None, - fuse_id: int = 0, - length: int = 0, - flags: int = 0, - data: Optional[List[int]] = None, - ) -> None: - """Message used to sign and send to device with EdgeLock. - - :param cert_ver: Certificate version, defaults to 0 - :param permissions: Certificate permission, to be used in future - The stated permission must allow the operation requested by the signed message - , defaults to 0 - :param issue_date: Issue date, defaults to None (Current date will be applied) - :param unique_id: UUID of device (least 64 bits is used), defaults to None - :param fuse_id: Fuse ID, defaults to 0 - :param length: Fuse length, defaults to 0 - :param flags: Fuse flags, defaults to 0 - :param data: List of fuse values - """ - super().__init__( - cert_ver=cert_ver, - permissions=permissions, - issue_date=issue_date, - cmd=self.TAG, - unique_id=unique_id, - ) - self.fuse_id = fuse_id - self.length = length - self.flags = flags - self.fuse_data: List[int] = data or [] - - def __str__(self) -> str: - ret = super().__str__() - ret += f" Fuse Index: {hex(self.fuse_id)}, {self.fuse_id}\n" - ret += f" Fuse Length: {self.length}\n" - ret += f" Fuse Flags: {hex(self.flags)}\n" - for i, data in enumerate(self.fuse_data): - ret += f" Fuse{i} Value: 0x{data:08X}" - return ret - - @property - def payload_len(self) -> int: - """Message payload length in bytes.""" - return 4 + len(self.fuse_data) * 4 - - def export_payload(self) -> bytes: - """Exports message payload to bytes array. - - :return: Bytes representation of message payload. - """ - payload = pack(self.PAYLOAD_FORMAT, self.fuse_id, self.length, self.flags) - for data in self.fuse_data: - payload += data.to_bytes(4, Endianness.LITTLE.value) - return payload - - def parse_payload(self, data: bytes) -> None: - """Parse payload. - - :param data: Binary data with Payload to parse. - """ - self.fuse_id, self.length, self.flags = unpack(self.PAYLOAD_FORMAT, data[:4]) - self.fuse_data.clear() - for i in range(self.length): - self.fuse_data.append( - int.from_bytes(data[4 + i * 4 : 8 + i * 4], Endianness.LITTLE.value) - ) - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Message": - """Converts the configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKError: Invalid configuration detected. - :return: Message object. - """ - command = config.get("command", {}) - if not isinstance(command, dict) or len(command) != 1: - raise SPSDKError(f"Invalid config field command: {command}") - command_name = list(command.keys())[0] - if MessageCommands.from_label(command_name) != MessageWriteSecureFuse.TAG: - raise SPSDKError( - "Invalid configuration for Write secure fuse Request command." - ) - - cert_ver, permission, issue_date, uuid = Message.load_from_config_generic( - config - ) - - secure_fuse = command.get("WRITE_SEC_FUSE_REQ") - assert isinstance(secure_fuse, dict) - fuse_id = secure_fuse.get("id") - assert isinstance(fuse_id, int) - flags: int = secure_fuse.get("flags", 0) - data_list: List = secure_fuse.get("data", []) - data = [] - for x in data_list: - data.append(value_to_int(x)) - length = len(data_list) - return MessageWriteSecureFuse( - cert_ver=cert_ver, - permissions=permission, - issue_date=issue_date, - unique_id=uuid, - fuse_id=fuse_id, - length=length, - flags=flags, - data=data, - ) - - def create_config(self) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :return: Configuration dictionary. - """ - cfg = self._create_general_config() - write_fuse_cfg: Dict[str, Any] = {} - cmd_cfg = {} - write_fuse_cfg["id"] = self.fuse_id - write_fuse_cfg["flags"] = self.flags - write_fuse_cfg["data"] = [f"0x{x:08X}" for x in self.fuse_data] - - cmd_cfg[MessageCommands.get_label(self.TAG)] = write_fuse_cfg - cfg["command"] = cmd_cfg - - return cfg - - def validate(self) -> None: - """Validate general message properties.""" - super().validate() - if self.fuse_data is None: - raise SPSDKValueError( - "Message Write secure fuse request: Missing fuse data" - ) - if len(self.fuse_data) != self.length: - raise SPSDKValueError( - "Message Write secure fuse request: The fuse value list " - f"has invalid length: ({len(self.fuse_data)} != {self.length})" - ) - - for i, val in enumerate(self.fuse_data): - if val >= 1 << 32: - raise SPSDKValueError( - f"Message Write secure fuse request: The fuse value({i}) is bigger than 32 bit: ({val})" - ) - - -class MessageKeyStoreReprovisioningEnable(Message): - """Key store reprovisioning enable request message class representation.""" - - TAG = MessageCommands.KEYSTORE_REPROVISIONING_ENABLE_REQ.tag - PAYLOAD_LENGTH = 12 - PAYLOAD_FORMAT = LITTLE_ENDIAN + UINT8 + UINT8 + UINT16 + UINT32 + UINT32 - - FLAGS = 0 # 0 : HSM storage. - TARGET = 0 # Target ELE - - def __init__( - self, - cert_ver: int = 0, - permissions: int = 0, - issue_date: Optional[int] = None, - unique_id: Optional[bytes] = None, - monotonic_counter: int = 0, - user_sab_id: int = 0, - ) -> None: - """Key store reprovisioning enable signed message class init. - - :param cert_ver: Certificate version, defaults to 0 - :param permissions: Certificate permission, to be used in future - The stated permission must allow the operation requested by the signed message - , defaults to 0 - :param issue_date: Issue date, defaults to None (Current date will be applied) - :param unique_id: UUID of device (least 64 bits is used), defaults to None - :param monotonic_counter: Monotonic counter value, defaults to 0 - :param user_sab_id: User SAB id, defaults to 0 - """ - super().__init__( - cert_ver=cert_ver, - permissions=permissions, - issue_date=issue_date, - cmd=self.TAG, - unique_id=unique_id, - ) - self.flags = self.FLAGS - self.target = self.TARGET - self.reserved = RESERVED - self.monotonic_counter = monotonic_counter - self.user_sab_id = user_sab_id - - def export_payload(self) -> bytes: - """Exports message payload to bytes array. - - :return: Bytes representation of message payload. - """ - return pack( - self.PAYLOAD_FORMAT, - self.flags, - self.target, - self.reserved, - self.monotonic_counter, - self.user_sab_id, - ) - - def parse_payload(self, data: bytes) -> None: - """Parse payload. - - :param data: Binary data with Payload to parse. - """ - ( - self.flags, - self.target, - self.reserved, - self.monotonic_counter, - self.user_sab_id, - ) = unpack(self.PAYLOAD_FORMAT, data[: self.PAYLOAD_LENGTH]) - - def validate(self) -> None: - """Validate general message properties.""" - super().validate() - if self.flags != self.FLAGS: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid flags: {self.flags}" - ) - if self.target != self.TARGET: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid target: {self.target}" - ) - if self.reserved != RESERVED: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid reserved field: {self.reserved}" - ) - if self.monotonic_counter >= 1 << 32: - raise SPSDKValueError( - "Message Key store reprovisioning enable request: Invalid monotonic " - f"counter field (not fit in 32bit): {self.monotonic_counter}" - ) - - if self.user_sab_id >= 1 << 32: - raise SPSDKValueError( - "Message Key store reprovisioning enable request: Invalid user SAB ID " - f"field (not fit in 32bit): {self.user_sab_id}" - ) - - def __str__(self) -> str: - ret = super().__str__() - ret += f" Monotonic counter value: 0x{self.monotonic_counter:08X}, {self.monotonic_counter}\n" - ret += ( - f" User SAB id: 0x{self.user_sab_id:08X}, {self.user_sab_id}" - ) - return ret - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Message": - """Converts the configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKError: Invalid configuration detected. - :return: Message object. - """ - command = config.get("command", {}) - if not isinstance(command, dict) or len(command) != 1: - raise SPSDKError(f"Invalid config field command: {command}") - command_name = list(command.keys())[0] - if ( - MessageCommands.from_label(command_name) - != MessageKeyStoreReprovisioningEnable.TAG - ): - raise SPSDKError( - "Invalid configuration for Write secure fuse Request command." - ) - - cert_ver, permission, issue_date, uuid = Message.load_from_config_generic( - config - ) - - keystore_repr_en = command.get("KEYSTORE_REPROVISIONING_ENABLE_REQ") - assert isinstance(keystore_repr_en, dict) - monotonic_counter = value_to_int(keystore_repr_en.get("monotonic_counter", 0)) - user_sab_id = value_to_int(keystore_repr_en.get("user_sab_id", 0)) - return MessageKeyStoreReprovisioningEnable( - cert_ver=cert_ver, - permissions=permission, - issue_date=issue_date, - unique_id=uuid, - monotonic_counter=monotonic_counter, - user_sab_id=user_sab_id, - ) - - def create_config(self) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :return: Configuration dictionary. - """ - cfg = self._create_general_config() - keystore_repr_en_cfg: Dict[str, Any] = {} - cmd_cfg = {} - keystore_repr_en_cfg["monotonic_counter"] = f"0x{self.monotonic_counter:08X}" - keystore_repr_en_cfg["user_sab_id"] = f"0x{self.user_sab_id:08X}" - - cmd_cfg[MessageCommands.get_label(self.TAG)] = keystore_repr_en_cfg - cfg["command"] = cmd_cfg - - return cfg - - -class MessageKeyExchange(Message): - """Key exchange request message class representation.""" - - TAG = MessageCommands.KEY_EXCHANGE_REQ.tag - PAYLOAD_LENGTH = 27 * 4 - PAYLOAD_VERSION = 0x07 - PAYLOAD_FORMAT = ( - LITTLE_ENDIAN - + UINT8 # TAG - + UINT8 # Version - + UINT16 # Reserved - + UINT32 # Key store ID - + UINT32 # Key exchange algorithm - + UINT16 # Salt Flags - + UINT16 # Derived key group - + UINT16 # Derived key size bits - + UINT16 # Derived key type - + UINT32 # Derived key lifetime - + UINT32 # Derived key usage - + UINT32 # Derived key permitted algorithm - + UINT32 # Derived key lifecycle - + UINT32 # Derived key ID - + UINT32 # Private key ID - + "32s" # Input peer public key digest word [0-7] - + "32s" # Input user fixed info digest word [0-7] - ) - - class KeyExchangeAlgorithm(SpsdkEnum): - """Key Exchange Algorithm valid values.""" - - HKDF_SHA256 = (0x09020109, "HKDF SHA256") - HKDF_SHA384 = (0x0902010A, "HKDF SHA384") - - class KeyDerivationAlgorithm(SpsdkEnum): - """Key Derivation Algorithm valid values.""" - - HKDF_SHA256 = (0x08000109, "HKDF SHA256", "HKDF SHA256 (HMAC two-step)") - HKDF_SHA384 = (0x0800010A, "HKDF SHA384", "HKDF SHA384 (HMAC two-step)") - - class DerivedKeyType(SpsdkEnum): - """Derived Key Type valid values.""" - - AES = (0x2400, "AES SHA256", "Possible bit widths: 128/192/256") - HMAC = (0x1100, "HMAC SHA384", "Possible bit widths: 224/256/384/512") - OEM_IMPORT_MK_SK = ( - 0x9200, - "OEM_IMPORT_MK_SK", - "Possible bit widths: 128/192/256", - ) - - class LifeCycle(SpsdkEnum): - """Chip life cycle valid values.""" - - CURRENT = (0x00, "CURRENT", "Current device lifecycle") - OPEN = (0x01, "OPEN") - CLOSED = (0x02, "CLOSED") - LOCKED = (0x04, "LOCKED") - - class LifeTime(SpsdkEnum): - """Edgelock Enclave life time valid values.""" - - VOLATILE = (0x00, "VOLATILE", "Standard volatile key") - PERSISTENT = (0x01, "PERSISTENT", "Standard persistent key") - PERMANENT = (0xFF, "PERMANENT", "Standard permanent key") - - class DerivedKeyUsage(SpsdkEnum): - """Derived Key Usage valid values.""" - - CACHE = ( - 0x00000004, - "Cache", - ( - "Permission to cache the key in the ELE internal secure memory. " - "This usage is set by default by ELE FW for all keys generated or imported." - ), - ) - ENCRYPT = ( - 0x00000100, - "Encrypt", - ( - "Permission to encrypt a message with the key. It could be cipher encryption," - " AEAD encryption or asymmetric encryption operation." - ), - ) - DECRYPT = ( - 0x00000200, - "Decrypt", - ( - "Permission to decrypt a message with the key. It could be cipher decryption," - " AEAD decryption or asymmetric decryption operation." - ), - ) - SIGN_MSG = ( - 0x00000400, - "Sign message", - ( - "Permission to sign a message with the key. It could be a MAC generation or an " - "asymmetric message signature operation." - ), - ) - VERIFY_MSG = ( - 0x00000800, - "Verify message", - ( - "Permission to verify a message signature with the key. It could be a MAC " - "verification or an asymmetric message signature verification operation." - ), - ) - SIGN_HASH = ( - 0x00001000, - "Sign hash", - ( - "Permission to sign a hashed message with the key with an asymmetric signature " - "operation. Setting this permission automatically sets the Sign Message usage." - ), - ) - VERIFY_HASH = ( - 0x00002000, - "Sign message", - ( - "Permission to verify a hashed message signature with the key with an asymmetric " - "signature verification operation. Setting this permission automatically sets the Verify Message usage." - ), - ) - DERIVE = ( - 0x00004000, - "Derive", - "Permission to derive other keys from this key.", - ) - - def __init__( - self, - cert_ver: int = 0, - permissions: int = 0, - issue_date: Optional[int] = None, - unique_id: Optional[bytes] = None, - key_store_id: int = 0, - key_exchange_algorithm: KeyExchangeAlgorithm = KeyExchangeAlgorithm.HKDF_SHA256, - salt_flags: int = 0, - derived_key_grp: int = 0, - derived_key_size_bits: int = 0, - derived_key_type: DerivedKeyType = DerivedKeyType.AES, - derived_key_lifetime: LifeTime = LifeTime.PERSISTENT, - derived_key_usage: Optional[List[DerivedKeyUsage]] = None, - derived_key_permitted_algorithm: KeyDerivationAlgorithm = KeyDerivationAlgorithm.HKDF_SHA256, - derived_key_lifecycle: LifeCycle = LifeCycle.OPEN, - derived_key_id: int = 0, - private_key_id: int = 0, - input_peer_public_key_digest: bytes = bytes(), - input_user_fixed_info_digest: bytes = bytes(), - ) -> None: - """Key exchange signed message class init. - - :param cert_ver: Certificate version, defaults to 0 - :param permissions: Certificate permission, to be used in future - The stated permission must allow the operation requested by the signed message - , defaults to 0 - :param issue_date: Issue date, defaults to None (Current date will be applied) - :param unique_id: UUID of device (least 64 bits is used), defaults to None - :param key_store_id: Key store ID where to store the derived key. It must be the key store ID - related to the key management handle set in the command API, defaults to 0 - :param key_exchange_algorithm: Algorithm used by the key exchange process: - - | HKDF SHA256 0x09020109 - | HKDF SHA384 0x0902010A - | , defaults to HKDF_SHA256 - - :param salt_flags: Bit field indicating the requested operations: - - | Bit 0: Salt in step #1 (HKDF-extract) of HMAC based two-step key derivation process: - | - 0: Use zeros salt; - | - 1:Use peer public key hash as salt; - | Bit 1: In case of ELE import, salt used to derive OEM_IMPORT_WRAP_SK and OEM_IMPORT_CMAC_SK: - | - 0: Zeros string; - | - 1: Device SRKH. - | Bit 2 to 15: Reserved, defaults to 0 - - :param derived_key_grp: Derived key group. 100 groups are available per key store. It must be a - value in the range [0; 99]. Keys belonging to the same group can be managed through - the Manage key group command, defaults to 0 - :param derived_key_size_bits: Derived key size bits attribute, defaults to 0 - :param derived_key_type: - - +-------------------+-------+------------------+ - |Key type | Value | Key size in bits | - +===================+=======+==================+ - | AES |0x2400 | 128/192/256 | - +-------------------+-------+------------------+ - | HMAC |0x1100 | 224/256/384/512 | - +-------------------+-------+------------------+ - | OEM_IMPORT_MK_SK* |0x9200 | 128/192/256 | - +-------------------+-------+------------------+ - - , defaults to AES - - :param derived_key_lifetime: Derived key lifetime attribute - - | VOLATILE 0x00 Standard volatile key. - | PERSISTENT 0x01 Standard persistent key. - | PERMANENT 0xFF Standard permanent key., defaults to PERSISTENT - - :param derived_key_usage: Derived key usage attribute. - - | Cache 0x00000004 Permission to cache the key in the ELE internal secure memory. - | This usage is set by default by ELE FW for all keys generated or imported. - | Encrypt 0x00000100 Permission to encrypt a message with the key. It could be cipher - | encryption, AEAD encryption or asymmetric encryption operation. - | Decrypt 0x00000200 Permission to decrypt a message with the key. It could be - | cipher decryption, AEAD decryption or asymmetric decryption operation. - | Sign message 0x00000400 Permission to sign a message with the key. It could be - | a MAC generation or an asymmetric message signature operation. - | Verify message 0x00000800 Permission to verify a message signature with the key. - | It could be a MAC verification or an asymmetric message signature - | verification operation. - | Sign hash 0x00001000 Permission to sign a hashed message with the key - | with an asymmetric signature operation. Setting this permission automatically - | sets the Sign Message usage. - | Verify hash 0x00002000 Permission to verify a hashed message signature with - | the key with an asymmetric signature verification operation. - | Setting this permission automatically sets the Verify Message usage. - | Derive 0x00004000 Permission to derive other keys from this key. - | , defaults to 0 - - :param derived_key_permitted_algorithm: Derived key permitted algorithm attribute - - | HKDF SHA256 (HMAC two-step) 0x08000109 - | HKDF SHA384 (HMAC two-step) 0x0800010A, defaults to HKDF_SHA256 - - :param derived_key_lifecycle: Derived key lifecycle attribute - - | CURRENT 0x00 Key is usable in current lifecycle. - | OPEN 0x01 Key is usable in open lifecycle. - | CLOSED 0x02 Key is usable in closed lifecycle. - | CLOSED and LOCKED 0x04 Key is usable in closed and locked lifecycle. - | , defaults to OPEN - - :param derived_key_id: Derived key ID attribute. It could be: - - - Wanted key identifier of the generated key: only supported by persistent - and permanent keys; - - 0x00000000 to let the FW chose the key identifier: supported by all - keys (all persistence levels). , defaults to 0 - - :param private_key_id: Identifier in the ELE key storage of the private key to use with the peer - public key during the key agreement process, defaults to 0 - :param input_peer_public_key_digest: Input peer public key digest buffer. - The algorithm used to generate the digest must be SHA256, defaults to list(8) - :param input_user_fixed_info_digest: Input user fixed info digest buffer. - The algorithm used to generate the digest must be SHA256, defaults to list(8) - """ - super().__init__( - cert_ver=cert_ver, - permissions=permissions, - issue_date=issue_date, - cmd=self.TAG, - unique_id=unique_id, - ) - self.tag = self.TAG - self.version = self.PAYLOAD_VERSION - self.reserved = RESERVED - self.key_store_id = key_store_id - self.key_exchange_algorithm = key_exchange_algorithm - self.salt_flags = salt_flags - self.derived_key_grp = derived_key_grp - self.derived_key_size_bits = derived_key_size_bits - self.derived_key_type = derived_key_type - self.derived_key_lifetime = derived_key_lifetime - self.derived_key_usage = derived_key_usage or [] - self.derived_key_permitted_algorithm = derived_key_permitted_algorithm - self.derived_key_lifecycle = derived_key_lifecycle - self.derived_key_id = derived_key_id - self.private_key_id = private_key_id - self.input_peer_public_key_digest = input_peer_public_key_digest - self.input_user_fixed_info_digest = input_user_fixed_info_digest - - def export_payload(self) -> bytes: - """Exports message payload to bytes array. - - :return: Bytes representation of message payload. - """ - derived_key_usage = 0 - for usage in self.derived_key_usage: - derived_key_usage |= usage.tag - return pack( - self.PAYLOAD_FORMAT, - self.tag, - self.version, - self.reserved, - self.key_store_id, - self.key_exchange_algorithm.tag, - self.derived_key_grp, - self.salt_flags, - self.derived_key_type.tag, - self.derived_key_size_bits, - self.derived_key_lifetime.tag, - derived_key_usage, - self.derived_key_permitted_algorithm.tag, - self.derived_key_lifecycle.tag, - self.derived_key_id, - self.private_key_id, - self.input_peer_public_key_digest, - self.input_user_fixed_info_digest, - ) - - def parse_payload(self, data: bytes) -> None: - """Parse payload. - - :param data: Binary data with Payload to parse. - """ - ( - self.tag, - self.version, - self.reserved, - self.key_store_id, - key_exchange_algorithm, - self.derived_key_grp, - self.salt_flags, - derived_key_type, - self.derived_key_size_bits, - derived_key_lifetime, - derived_key_usage, - derived_key_permitted_algorithm, - derived_key_lifecycle, - self.derived_key_id, - self.private_key_id, - input_peer_public_key_digest, - input_user_fixed_info_digest, - ) = unpack(self.PAYLOAD_FORMAT, data[: self.PAYLOAD_LENGTH]) - - # Do some post process - self.key_exchange_algorithm = self.KeyExchangeAlgorithm.from_tag( - key_exchange_algorithm - ) - self.derived_key_type = self.DerivedKeyType.from_tag(derived_key_type) - self.derived_key_lifetime = self.LifeTime.from_tag(derived_key_lifetime) - self.derived_key_permitted_algorithm = self.KeyDerivationAlgorithm.from_tag( - derived_key_permitted_algorithm - ) - self.derived_key_lifecycle = self.LifeCycle.from_tag(derived_key_lifecycle) - - self.input_peer_public_key_digest = input_peer_public_key_digest - self.input_user_fixed_info_digest = input_user_fixed_info_digest - self.derived_key_usage.clear() - for tag in self.DerivedKeyUsage.tags(): - if tag & derived_key_usage: - self.derived_key_usage.append(self.DerivedKeyUsage.from_tag(tag)) - - def validate(self) -> None: - """Validate general message properties.""" - super().validate() - if self.tag != self.TAG: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid tag: {self.tag}" - ) - if self.version != self.version: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid verssion: {self.version}" - ) - if self.reserved != RESERVED: - raise SPSDKValueError( - f"Message Key store reprovisioning enable request: Invalid reserved field: {self.reserved}" - ) - - def __str__(self) -> str: - ret = super().__str__() - ret += f" KeyStore ID value: 0x{self.key_store_id:08X}, {self.key_store_id}\n" - ret += f" Key exchange algorithm value: {self.key_exchange_algorithm.label}\n" - ret += f" Salt flags value: 0x{self.salt_flags:08X}, {self.salt_flags}\n" - ret += f" Derived key group value: 0x{self.derived_key_grp:08X}, {self.derived_key_grp}\n" - ret += f" Derived key bit size value: 0x{self.derived_key_size_bits:08X}, {self.derived_key_size_bits}\n" - ret += f" Derived key type value: {self.derived_key_type.label}\n" - ret += f" Derived key life time value: {self.derived_key_lifetime.label}\n" - ret += ( - f" Derived key usage value: {[x.label for x in self.derived_key_usage]}\n" - ) - ret += f" Derived key permitted algorithm value: {self.derived_key_permitted_algorithm.label}\n" - ret += f" Derived key life cycle value: {self.derived_key_lifecycle.label}\n" - ret += f" Derived key ID value: 0x{self.derived_key_id:08X}, {self.derived_key_id}\n" - ret += f" Private key ID value: 0x{self.private_key_id:08X}, {self.private_key_id}\n" - ret += f" Input peer public key digest value: {self.input_peer_public_key_digest.hex()}\n" - ret += f" Input user public fixed info digest value: {self.input_peer_public_key_digest.hex()}\n" - return ret - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "Message": - """Converts the configuration option into an message object. - - "config" content of container configurations. - - :param config: Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :raises SPSDKError: Invalid configuration detected. - :return: Message object. - """ - command = config.get("command", {}) - if not isinstance(command, dict) or len(command) != 1: - raise SPSDKError(f"Invalid config field command: {command}") - command_name = list(command.keys())[0] - if MessageCommands.from_label(command_name) != MessageKeyExchange.TAG: - raise SPSDKError("Invalid configuration forKey Exchange Request command.") - - cert_ver, permission, issue_date, uuid = Message.load_from_config_generic( - config - ) - - key_exchange = command.get("KEY_EXCHANGE_REQ") - assert isinstance(key_exchange, dict) - - key_store_id = value_to_int(key_exchange.get("key_store_id", 0)) - key_exchange_algorithm = MessageKeyExchange.KeyExchangeAlgorithm.from_attr( - key_exchange.get("key_exchange_algorithm", "HKDF SHA256") - ) - salt_flags = value_to_int(key_exchange.get("salt_flags", 0)) - derived_key_grp = value_to_int(key_exchange.get("derived_key_grp", 0)) - derived_key_size_bits = value_to_int( - key_exchange.get("derived_key_size_bits", 128) - ) - derived_key_type = MessageKeyExchange.DerivedKeyType.from_attr( - key_exchange.get("derived_key_type", "AES SHA256") - ) - derived_key_lifetime = MessageKeyExchange.LifeTime.from_attr( - key_exchange.get("derived_key_lifetime", "PERSISTENT") - ) - derived_key_usage = [ - MessageKeyExchange.DerivedKeyUsage.from_attr(x) - for x in key_exchange.get("derived_key_usage", []) - ] - derived_key_permitted_algorithm = ( - MessageKeyExchange.KeyDerivationAlgorithm.from_attr( - key_exchange.get("derived_key_permitted_algorithm", "HKDF SHA256") - ) - ) - derived_key_lifecycle = MessageKeyExchange.LifeCycle.from_attr( - key_exchange.get("derived_key_lifecycle", "OPEN") - ) - derived_key_id = value_to_int(key_exchange.get("derived_key_id", 0)) - private_key_id = value_to_int(key_exchange.get("private_key_id", 0)) - input_peer_public_key_digest = load_hex_string( - source=key_exchange.get("input_peer_public_key_digest", bytes(32)), - expected_size=32, - search_paths=search_paths, - ) - input_user_fixed_info_digest = load_hex_string( - source=key_exchange.get("input_user_fixed_info_digest", bytes(32)), - expected_size=32, - search_paths=search_paths, - ) - - return MessageKeyExchange( - cert_ver=cert_ver, - permissions=permission, - issue_date=issue_date, - unique_id=uuid, - key_store_id=key_store_id, - key_exchange_algorithm=key_exchange_algorithm, - salt_flags=salt_flags, - derived_key_grp=derived_key_grp, - derived_key_size_bits=derived_key_size_bits, - derived_key_type=derived_key_type, - derived_key_lifetime=derived_key_lifetime, - derived_key_usage=derived_key_usage, - derived_key_permitted_algorithm=derived_key_permitted_algorithm, - derived_key_lifecycle=derived_key_lifecycle, - derived_key_id=derived_key_id, - private_key_id=private_key_id, - input_peer_public_key_digest=input_peer_public_key_digest, - input_user_fixed_info_digest=input_user_fixed_info_digest, - ) - - def create_config(self) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :return: Configuration dictionary. - """ - cfg = self._create_general_config() - key_exchange_cfg: Dict[str, Any] = {} - cmd_cfg = {} - key_exchange_cfg["key_store_id"] = f"0x{self.key_store_id:08X}" - key_exchange_cfg["key_exchange_algorithm"] = self.key_exchange_algorithm.label - key_exchange_cfg["salt_flags"] = f"0x{self.salt_flags:08X}" - key_exchange_cfg["derived_key_grp"] = self.derived_key_grp - key_exchange_cfg["derived_key_size_bits"] = self.derived_key_size_bits - key_exchange_cfg["derived_key_type"] = self.derived_key_type.label - key_exchange_cfg["derived_key_lifetime"] = self.derived_key_lifetime.label - key_exchange_cfg["derived_key_usage"] = [ - x.label for x in self.derived_key_usage - ] - key_exchange_cfg[ - "derived_key_permitted_algorithm" - ] = self.derived_key_permitted_algorithm.label - key_exchange_cfg["derived_key_lifecycle"] = self.derived_key_lifecycle.label - key_exchange_cfg["derived_key_id"] = self.derived_key_id - key_exchange_cfg["private_key_id"] = self.private_key_id - key_exchange_cfg[ - "input_peer_public_key_digest" - ] = self.input_peer_public_key_digest.hex() - key_exchange_cfg["input_user_fixed_info_digest"] = ( - self.input_user_fixed_info_digest.hex() - if self.input_user_fixed_info_digest - else bytes(32).hex() - ) - - cmd_cfg[MessageCommands.get_label(self.TAG)] = key_exchange_cfg - cfg["command"] = cmd_cfg - - return cfg - - -class SignedMessage(AHABContainerBase): - """Class representing the Signed message. - - Signed Message:: - - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Tag | Length (MSB) | Length (LSB) | Version | - +-----+--------------+--------------+----------------+----------------+ - |0x04 | Flags | - +-----+--------------+--------------+---------------------------------+ - |0x08 | Reserved | Fuse version | Software version | - +-----+--------------+--------------+---------------------------------+ - |0x10 | Message descriptor | - +-----+---------------------------------------------------------------+ - |0x34 | Message header | - +-----+---------------------------------------------------------------+ - |0x44 | Message payload | - +-----+---------------------------------------------------------------+ - |0xXX | Signature Block | - +-----+---------------------------------------------------------------+ - - Message descriptor:: - +-----+--------------+--------------+----------------+----------------+ - |Off | Byte 3 | Byte 2 | Byte 1 | Byte 0 | - +-----+--------------+--------------+----------------+----------------+ - |0x00 | Reserved | Flags | - +-----+----------------------------------------------+----------------+ - |0x04 | IV (256 bits) | - +-----+---------------------------------------------------------------+ - - """ - - TAG = SignedMessageTags.SIGNED_MSG.tag - ENCRYPT_IV_LEN = 32 - - def __init__( - self, - flags: int = 0, - fuse_version: int = 0, - sw_version: int = 0, - message: Optional[Message] = None, - signature_block: Optional[SignatureBlock] = None, - encrypt_iv: Optional[bytes] = None, - ): - """Class object initializer. - - :param flags: flags. - :param fuse_version: value must be equal to or greater than the version - stored in the fuses to allow loading this container. - :param sw_version: used by PHBC (Privileged Host Boot Companion) to select - between multiple images with same fuse version field. - :param message: Message command to be signed. - :param signature_block: signature block. - :param encrypt_iv: Encryption Initial Vector - if defined the encryption is used. - """ - super().__init__( - flags=flags, - fuse_version=fuse_version, - sw_version=sw_version, - signature_block=signature_block, - ) - self.message = message - self.encrypt_iv = encrypt_iv - - def __eq__(self, other: object) -> bool: - if isinstance(other, SignedMessage): - if super().__eq__(other) and self.message == other.message: - return True - - return False - - def __repr__(self) -> str: - return f"Signed Message, {'Encrypted' if self.encrypt_iv else 'Plain'}" - - def __str__(self) -> str: - return ( - f" Flags: {hex(self.flags)}\n" - f" Fuse version: {hex(self.fuse_version)}\n" - f" SW version: {hex(self.sw_version)}\n" - f" Signature Block:\n{str(self.signature_block)}\n" - f" Message:\n{str(self.message)}\n" - f" Encryption IV: {self.encrypt_iv.hex() if self.encrypt_iv else 'Not Available'}" - ) - - @property - def _signature_block_offset(self) -> int: - """Returns current signature block offset. - - :return: Offset in bytes of Signature block. - """ - # Constant size of Container header + Image array Entry table - assert self.message - return calcsize(self.format()) + len(self.message) - - def __len__(self) -> int: - """Get total length of AHAB container. - - :return: Size in bytes of Message. - """ - return self._signature_block_offset + len(self.signature_block) - - @classmethod - def format(cls) -> str: - """Format of binary representation.""" - return ( - super().format() - + UINT8 # Descriptor Flags - + UINT8 # Reserved - + UINT16 # Reserved - + "32s" # IV - Initial Vector if encryption is enabled - ) - - def update_fields(self) -> None: - """Updates all volatile information in whole container structure. - - :raises SPSDKError: When inconsistent image array length is detected. - """ - # 0. Update length - self.length = len(self) - # 1. Update the signature block to get overall size of it - self.signature_block.update_fields() - # 2. Sign the image header - if self.flag_srk_set != "none": - assert self.signature_block.signature - self.signature_block.signature.sign(self.get_signature_data()) - - def _export(self) -> bytes: - """Export raw data without updates fields into bytes. - - :return: bytes representing container header content including the signature block. - """ - signed_message = pack( - self.format(), - self.version, - len(self), - self.tag, - self.flags, - self.sw_version, - self.fuse_version, - RESERVED, - self._signature_block_offset, - RESERVED, # Reserved field - 1 if self.encrypt_iv else 0, - RESERVED, - RESERVED, - self.encrypt_iv if self.encrypt_iv else bytes(32), - ) - # Add Message Header + Message Payload - assert self.message - signed_message += self.message.export() - # Add Signature Block - signed_message += align_block( - self.signature_block.export(), CONTAINER_ALIGNMENT - ) - return signed_message - - def export(self) -> bytes: - """Export the signed image into one chunk. - - :raises SPSDKValueError: if the number of images doesn't correspond the the number of - entries in image array info. - :return: images exported into single binary - """ - self.update_fields() - self.validate({}) - return self._export() - - def validate(self, data: Dict[str, Any]) -> None: - """Validate object data. - - :param data: Additional validation data. - :raises SPSDKValueError: Invalid any value of Image Array entry - """ - data["flag_used_srk_id"] = self.flag_used_srk_id - - if self.length != len(self): - raise SPSDKValueError( - f"Container Header: Invalid block length: {self.length} != {len(self)}" - ) - super().validate(data) - if self.encrypt_iv and len(self.encrypt_iv) != self.ENCRYPT_IV_LEN: - raise SPSDKValueError( - "Signed Message: Invalid Encryption initialization vector length: " - f"{len(self.encrypt_iv)*8} Bits != {self.ENCRYPT_IV_LEN * 8} Bits" - ) - if self.message is None: - raise SPSDKValueError("Signed Message: Invalid Message payload.") - self.message.validate() - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse input binary to the signed message object. - - :param data: Binary data with Container block to parse. - :return: Object recreated from the binary data. - """ - SignedMessage.check_container_head(data) - image_format = SignedMessage.format() - ( - _, # version - _, # container_length - _, # tag - flags, - sw_version, - fuse_version, - _, # number_of_images - signature_block_offset, - _, # reserved - descriptor_flags, - _, # reserved - _, # reserved - iv, - ) = unpack(image_format, data[: SignedMessage.fixed_length()]) - - parsed_signed_msg = cls( - flags=flags, - fuse_version=fuse_version, - sw_version=sw_version, - encrypt_iv=iv if bool(descriptor_flags & 0x01) else None, - ) - parsed_signed_msg.signature_block = SignatureBlock.parse( - data[signature_block_offset:] - ) - - # Parse also Message itself - parsed_signed_msg.message = Message.parse( - data[SignedMessage.fixed_length() : signature_block_offset] - ) - return parsed_signed_msg - - def create_config(self, data_path: str) -> Dict[str, Any]: - """Create configuration of the Signed Message. - - :param data_path: Path to store the data files of configuration. - :return: Configuration dictionary. - """ - self.validate({}) - cfg = self._create_config(0, data_path) - cfg["family"] = "N/A" - cfg["revision"] = "N/A" - cfg["output"] = "N/A" - - assert self.message - cfg["message"] = self.message.create_config() - - return cfg - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "SignedMessage": - """Converts the configuration option into an Signed message object. - - "config" content of container configurations. - - :param config: Signed Message configuration dictionaries. - :param search_paths: List of paths where to search for the file, defaults to None - :return: Message object. - """ - signed_msg = SignedMessage() - signed_msg.search_paths = search_paths or [] - AHABContainerBase.load_from_config_generic(signed_msg, config) - - message = config.get("message") - assert isinstance(message, dict) - - signed_msg.message = Message.load_from_config( - message, search_paths=search_paths - ) - - return signed_msg - - def image_info(self) -> BinaryImage: - """Get Image info object. - - :return: Signed Message Info object. - """ - self.validate({}) - assert self.message - ret = BinaryImage( - name="Signed Message", - size=len(self), - offset=0, - binary=self.export(), - description=( - f"Signed Message for {MessageCommands.get_label(self.message.TAG)}" - ), - ) - return ret - - @staticmethod - def get_validation_schemas() -> List[Dict[str, Any]]: - """Get list of validation schemas. - - :return: Validation list of schemas. - """ - sch = DatabaseManager().db.get_schema_file(DatabaseManager.SIGNED_MSG) - sch["properties"]["family"]["enum"] = AHABImage.get_supported_families() - return [sch] - - @staticmethod - def generate_config_template( - family: str, message: Optional[MessageCommands] = None - ) -> Dict[str, Any]: - """Generate AHAB configuration template. - - :param family: Family for which the template should be generated. - :param message: Generate the template just for one message type, if not used , its generated for all messages - :return: Dictionary of individual templates (key is name of template, value is template itself). - """ - val_schemas = SignedMessage.get_validation_schemas() - val_schemas[0]["properties"]["family"]["template_value"] = family - - if family not in AHABImage.get_supported_families(): - raise SPSDKValueError( - f"Unsupported value for family: {family} not in {AHABImage.get_supported_families()}" - ) - - if message: - for cmd_sch in val_schemas[0]["properties"]["message"]["properties"][ - "command" - ]["oneOf"]: - cmd_sch["skip_in_template"] = bool( - message.label not in cmd_sch["properties"] - ) - - yaml_data = CommentedConfig( - f"Signed message Configuration template for {family}.", val_schemas - ).get_template() - - return {f"{family}_signed_msg": yaml_data} diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/utils.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/utils.py deleted file mode 100644 index e7044b36..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/ahab/utils.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""AHAB utils module.""" -import logging -from typing import Optional - -from ...apps.utils.utils import SPSDKError -from ...image.ahab.ahab_container import ( - AHABContainerBase, - AHABImage, - Blob, - SignatureBlock, -) -from ...utils.database import DatabaseManager, get_db -from ...utils.misc import load_binary - -logger = logging.getLogger(__name__) - - -def ahab_update_keyblob( - family: str, - binary: str, - keyblob: str, - container_id: int, - mem_type: Optional[str], -) -> None: - """Update keyblob in AHAB image. - - :param family: MCU family - :param binary: Path to AHAB image binary - :param keyblob: Path to keyblob - :param container_id: Index of the container to be updated - :param mem_type: Memory type used for bootable image - :raises SPSDKError: In case the container id not present - :raises SPSDKError: In case the AHAB image does not contain blob - :raises SPSDKError: In case the length of keyblobs don't match - """ - DATA_READ = 0x2000 - offset = 0 - if mem_type: - database = get_db(family) - offset = database.get_dict( - DatabaseManager.BOOTABLE_IMAGE, ["mem_types", mem_type, "segments"] - )["ahab_container"] - - keyblob_data = load_binary(keyblob) - image = AHABImage(family) - - try: - address = image.ahab_address_map[container_id] - except IndexError as exc: - raise SPSDKError(f"No container ID {container_id}") from exc - - with open(binary, "r+b") as f: - logger.debug( - f"Trying to find AHAB container header at offset {hex(address + offset)}" - ) - f.seek(address + offset) - data = f.read(DATA_READ) - ( - _, - _, - _, - _, - signature_block_offset, - ) = AHABContainerBase._parse(data) - f.seek(signature_block_offset + address + offset) - signature_block = SignatureBlock.parse(f.read(DATA_READ)) - blob = Blob.parse(keyblob_data) - blob.validate() - signature_block.update_fields() - signature_block.validate({}) - if not signature_block.blob: - raise SPSDKError("AHAB Container must contain BLOB in order to update it") - if not len(signature_block.blob.export()) == len(blob.export()): - raise SPSDKError("The size of the BLOB must be same") - logger.debug(f"AHAB container found at offset {hex(address + offset)}") - logger.debug(f"New keyblob: \n{blob}") - logger.debug(f"Old keyblob: \n{signature_block.blob}") - f.seek(signature_block_offset + address + signature_block._blob_offset + offset) - f.write(blob.export()) diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/header.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/header.py deleted file mode 100644 index d168dfdd..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/header.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2017-2018 Martin Olejar -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Header.""" - -from struct import calcsize, pack, unpack_from -from typing import Optional, Union - -from typing_extensions import Self - -from ..exceptions import SPSDKError, SPSDKParsingError -from ..utils.abstract import BaseClass -from ..utils.spsdk_enum import SpsdkEnum - -######################################################################################################################## -# Enums -######################################################################################################################## - - -class SegTag(SpsdkEnum): - """Segments Tag.""" - - XMCD = (0xC0, "XMCD", "External Memory Configuration Data") - DCD = (0xD2, "DCD", "Device Configuration Data") - CSF = (0xD4, "CSF", "Command Sequence File Data") - # i.MX6, i.MX7, i.MX8M - IVT2 = (0xD1, "IVT2", "Image Vector Table (Version 2)") - CRT = (0xD7, "CRT", "Certificate") - SIG = (0xD8, "SIG", "Signature") - EVT = (0xDB, "EVT", "Event") - RVT = (0xDD, "RVT", "ROM Vector Table") - WRP = (0x81, "WRP", "Wrapped Key") - MAC = (0xAC, "MAC", "Message Authentication Code") - # i.MX8QXP_A0, i.MX8QM_A0 - IVT3 = (0xDE, "IVT3", "Image Vector Table (Version 3)") - # i.MX8QXP_B0, i.MX8QM_B0 - BIC1 = (0x87, "BIC1", "Boot Images Container") - SIGB = (0x90, "SIGB", "Signature block") - - -class CmdTag(SpsdkEnum): - """CSF/DCD Command Tag.""" - - SET = (0xB1, "SET", "Set") - INS_KEY = (0xBE, "INS_KEY", "Install Key") - AUT_DAT = (0xCA, "AUT_DAT", "Authenticate Data") - WRT_DAT = (0xCC, "WRT_DAT", "Write Data") - CHK_DAT = (0xCF, "CHK_DAT", "Check Data") - NOP = (0xC0, "NOP", "No Operation (NOP)") - INIT = (0xB4, "INIT", "Initialize") - UNLK = (0xB2, "UNLK", "Unlock") - - -######################################################################################################################## -# Classes -######################################################################################################################## - - -class Header(BaseClass): - """Header element type.""" - - FORMAT = ">BHB" - SIZE = calcsize(FORMAT) - - @property - def size(self) -> int: - """Header size in bytes.""" - return self.SIZE - - def __init__( - self, tag: int = 0, param: int = 0, length: Optional[int] = None - ) -> None: - """Constructor. - - :param tag: section tag - :param param: TODO - :param length: length of the segment or command; if not specified, size of the header is used - :raises SPSDKError: If invalid length - """ - self._tag = tag - self.param: int = param - self.length: int = self.SIZE if length is None else length - if self.SIZE > self.length or self.length >= 65536: - raise SPSDKError("Invalid length") - - @property - def tag(self) -> int: - """:return: section tag: command tag or segment tag, ...""" - return self._tag - - @property - def tag_name(self) -> str: - """Returns the header's tag name.""" - return SegTag.get_label(self.tag) - - def __repr__(self) -> str: - return ( - f"{self.__class__.__name__}({self.tag_name}, {self.param}, {self.length})" - ) - - def __str__(self) -> str: - return ( - f"{self.__class__.__name__} " - ) - - def export(self) -> bytes: - """Binary representation of the header.""" - return pack(self.FORMAT, self.tag, self.length, self.param) - - @classmethod - def parse(cls, data: bytes, required_tag: Optional[int] = None) -> Self: - """Parse header. - - :param data: Raw data as bytes or bytearray - :param required_tag: Check header TAG if specified value or ignore if is None - :return: Header object - :raises SPSDKParsingError: if required header tag does not match - """ - tag, length, param = unpack_from(cls.FORMAT, data) - if required_tag is not None and tag != required_tag: - raise SPSDKParsingError( - f" Invalid header tag: '0x{tag:02X}' expected '0x{required_tag:02X}' " - ) - - return cls(tag, param, length) - - -class CmdHeader(Header): - """Command header.""" - - def __init__( - self, tag: Union[CmdTag, int], param: int = 0, length: Optional[int] = None - ) -> None: - """Constructor. - - :param tag: command tag - :param param: TODO - :param length: of the command binary section, in bytes - :raises SPSDKError: If invalid command tag - """ - tag = tag.tag if isinstance(tag, CmdTag) else tag - super().__init__(tag, param, length) - if tag not in CmdTag.tags(): - raise SPSDKError("Invalid command tag") - - @property - def tag(self) -> int: - """Command tag.""" - return self._tag - - @classmethod - def parse(cls, data: bytes, required_tag: Optional[int] = None) -> Self: - """Create Header from binary data. - - :param data: binary data to convert into header - :param required_tag: CmdTag, None if not required - :return: parsed instance - :raises SPSDKParsingError: If required header tag does not match - :raises SPSDKError: If invalid tag - """ - if required_tag is not None: - if required_tag not in CmdTag.tags(): - raise SPSDKError("Invalid tag") - return super(CmdHeader, cls).parse(data, required_tag) - - -class Header2(Header): - """Header element type.""" - - FORMAT = " bytes: - """Binary representation of the header.""" - return pack(self.FORMAT, self.param, self.length, self.tag) - - @classmethod - def parse(cls, data: bytes, required_tag: Optional[int] = None) -> Self: - """Parse header. - - :param data: Raw data as bytes or bytearray - :param required_tag: Check header TAG if specified value or ignore if is None - :raises SPSDKParsingError: Raises an error if required tag is empty or not valid - :return: Header2 object - """ - param, length, tag = unpack_from(cls.FORMAT, data) - if required_tag is not None and tag != required_tag: - raise SPSDKParsingError( - f" Invalid header tag: '0x{tag:02X}' expected '0x{required_tag:02X}' " - ) - - return cls(tag, param, length) diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/misc.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/misc.py deleted file mode 100644 index d592c340..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/misc.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2017-2018 Martin Olejar -# Copyright 2019-2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Misc.""" -import io -from io import SEEK_CUR -from typing import Optional, Union - -from ..exceptions import SPSDKError -from ..utils.registers import value_to_int -from .header import Header - - -class RawDataException(SPSDKError): - """Raw data read failed.""" - - -class StreamReadFailed(RawDataException): - """Read_raw_data could not read stream.""" - - -class NotEnoughBytesException(RawDataException): - """Read_raw_data could not read enough data.""" - - -def hexdump_fmt(data: bytes, tab: int = 4, length: int = 16, sep: str = ":") -> str: - """Dump some potentially larger data in hex.""" - text = " " * tab - for i, j in enumerate(data): - text += f"{j:02x}{sep}" - if ((i + 1) % length) == 0: - text += "\n" + " " * tab - return text - - -def modulus_fmt(modulus: bytes, tab: int = 4, length: int = 15, sep: str = ":") -> str: - """Modulus format.""" - return hexdump_fmt(b"\0" + modulus, tab, length, sep) - - -def read_raw_data( - stream: Union[io.BufferedReader, io.BytesIO], - length: int, - index: Optional[int] = None, - no_seek: bool = False, -) -> bytes: - """Read raw data.""" - if index is not None: - if index < 0: - raise SPSDKError(f" Index must be non-negative, found {index}") - if index != stream.tell(): - stream.seek(index) - - if length < 0: - raise SPSDKError(f" Length must be non-negative, found {length}") - - try: - data = stream.read(length) - except Exception as exc: - raise StreamReadFailed( - f" stream.read() failed, requested {length} bytes" - ) from exc - - if len(data) != length: - raise NotEnoughBytesException( - f" Could not read enough bytes, expected {length}, found {len(data)}" - ) - - if no_seek: - stream.seek(-length, SEEK_CUR) - - return data - - -def read_raw_segment( - buffer: Union[io.BufferedReader, io.BytesIO], - segment_tag: int, - index: Optional[int] = None, -) -> bytes: - """Read raw segment.""" - hrdata = read_raw_data(buffer, Header.SIZE, index) - length = Header.parse(hrdata, segment_tag).length - Header.SIZE - return hrdata + read_raw_data(buffer, length) - - -def dict_diff(main: dict, mod: dict) -> dict: - """Return a difference between two dictionaries if key is not present in main, it's skipped.""" - diff = {} - for key, value in mod.items(): - if isinstance(value, dict): - sub = dict_diff(main[key], value) - if sub: - diff[key] = sub - else: - if key not in main: - continue - main_value = main[key] if isinstance(main, dict) else main - try: - if value_to_int(main_value) != value_to_int(value): - diff[key] = value - except (SPSDKError, TypeError): - # Not a number! - if main_value != value: - diff[key] = value - return diff diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/image/secret.py b/pynitrokey/trussed/bootloader/lpc55_upload/image/secret.py deleted file mode 100644 index 11e78bba..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/image/secret.py +++ /dev/null @@ -1,951 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2017-2018 Martin Olejar -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Commands and responses used by SDP module.""" -import math -from hashlib import sha256 -from struct import pack, unpack, unpack_from -from typing import Any, Iterator, List, Optional, Union - -from typing_extensions import Self - -from ..crypto.certificate import Certificate, ExtensionNotFound -from ..crypto.keys import EccCurve, PublicKeyEcc, PublicKeyRsa, get_ecc_curve -from ..crypto.types import SPSDKKeyUsage -from ..exceptions import SPSDKError -from ..utils.abstract import BaseClass -from ..utils.misc import Endianness -from ..utils.spsdk_enum import SpsdkEnum -from .header import Header, SegTag -from .misc import hexdump_fmt, modulus_fmt - - -class EnumAlgorithm(SpsdkEnum): - """Algorithm types.""" - - ANY = (0x00, "ANY", "Algorithm type ANY") - HASH = (0x01, "HASH", "Hash algorithm type") - SIG = (0x02, "SIG", "Signature algorithm type") - F = (0x03, "F", "Finite field arithmetic") - EC = (0x04, "EC", "Elliptic curve arithmetic") - CIPHER = (0x05, "CIPHER", "Cipher algorithm type") - MODE = (0x06, "MODE", "Cipher/hash modes") - WRAP = (0x07, "WRAP", "Key wrap algorithm type") - # Hash algorithms - SHA1 = (0x11, "SHA1", "SHA-1 algorithm ID") - SHA256 = (0x17, "SHA256", "SHA-256 algorithm ID") - SHA512 = (0x1B, "SHA512", "SHA-512 algorithm ID") - # Signature algorithms - PKCS1 = (0x21, "PKCS1", "PKCS#1 RSA signature algorithm") - ECDSA = (0x27, "ECDSA", "NIST ECDSA signature algorithm") - # Cipher algorithms - AES = (0x55, "AES", "AES algorithm ID") - # Cipher or hash modes - CCM = (0x66, "CCM", "Counter with CBC-MAC") - # Key wrap algorithms - BLOB = (0x71, "BLOB", "SHW-specific key wrap") - - -class EnumSRK(SpsdkEnum): - """Entry type in the System Root Key Table.""" - - KEY_PUBLIC = (0xE1, "KEY_PUBLIC", "Public key type: data present") - KEY_HASH = (0xEE, "KEY_HASH", "Any key: hash only") - - -class BaseSecretClass(BaseClass): - """Base SPSDK class.""" - - def __init__(self, tag: SegTag, version: int = 0x40): - """Constructor. - - :param tag: section TAG - :param version: format version - """ - self._header = Header(tag=tag.tag, param=version) - - @property - def version(self) -> int: - """Format version.""" - return self._header.param - - @property - def version_major(self) -> int: - """Major format version.""" - return self.version >> 4 - - @property - def version_minor(self) -> int: - """Minor format version.""" - return self.version & 0xF - - @property - def size(self) -> int: - """Size of the exported binary data. - - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - -class SecretKeyBlob: - """Secret Key Blob.""" - - @property - def blob(self) -> bytes: - """Data of Secret Key Blob.""" - return self._data - - @blob.setter - def blob(self, value: Union[bytes, bytearray]) -> None: - assert isinstance(value, (bytes, bytearray)) - self._data = value - - @property - def size(self) -> int: - """Size of Secret Key Blob.""" - return len(self._data) + 4 - - def __init__(self, mode: int, algorithm: int, flag: int) -> None: - """Initialize Secret Key Blob.""" - self.mode = mode - self.algorithm = algorithm - self.flag = flag - self._data = bytearray() - - def __eq__(self, obj: Any) -> bool: - return isinstance(obj, SecretKeyBlob) and vars(obj) == vars(self) - - def __ne__(self, obj: Any) -> bool: - return not self.__eq__(obj) - - def __repr__(self) -> str: - return ( - f"SecKeyBlob " - ) - - def __str__(self) -> str: - """String representation of the Secret Key Blob.""" - msg = "-" * 60 + "\n" - msg += "SecKeyBlob\n" - msg += "-" * 60 + "\n" - msg += f"Mode: {self.mode}\n" - msg += f"Algorithm: {self.algorithm}\n" - msg += f"Flag: 0x{self.flag:02X}\n" - msg += f"Size: {len(self._data)} Bytes\n" - return msg - - def export(self) -> bytes: - """Export of Secret Key Blob.""" - raw_data = pack("4B", self.mode, self.algorithm, self.size, self.flag) - raw_data += bytes(self._data) - return raw_data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse of Secret Key Blob.""" - (mode, alg, size, flg) = unpack_from("4B", data) - obj = cls(mode, alg, flg) - obj.blob = data[4 : 4 + size] - return obj - - -class CertificateImg(BaseSecretClass): - """Certificate structure for bootable image.""" - - @property - def size(self) -> int: - """Size of Certificate structure for bootable image.""" - return Header.SIZE + len(self._data) - - def __init__(self, version: int = 0x40, data: Optional[bytes] = None) -> None: - """Initialize the certificate structure for bootable image.""" - super().__init__(SegTag.CRT, version) - self._data = bytearray() if data is None else bytearray(data) - - def __len__(self) -> int: - return len(self._data) - - def __getitem__(self, key: int) -> int: - return self._data[key] - - def __setitem__(self, key: int, value: int) -> None: - self._data[key] = value - - def __iter__(self) -> Iterator[int]: - return self._data.__iter__() - - def __repr__(self) -> str: - return f"Certificate " - - def __str__(self) -> str: - """String representation of the CertificateImg.""" - msg = "-" * 60 + "\n" - msg += ( - f"Certificate (Ver: {self.version >> 4:X}.{self.version & 0xF:X}, " - f"Size: {len(self._data)})\n" - ) - msg += "-" * 60 + "\n" - return msg - - def export(self) -> bytes: - """Export.""" - self._header.length = self.size - raw_data = self._header.export() - raw_data += self._data - return raw_data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse.""" - header = Header.parse(data, SegTag.CRT.tag) - return cls(header.param, data[Header.SIZE : header.length]) - - -class Signature(BaseSecretClass): - """Class representing a signature.""" - - @property - def size(self) -> int: - """Size of a signature.""" - return Header.SIZE + len(self._data) - - def __init__(self, version: int = 0x40, data: Optional[bytes] = None) -> None: - """Initialize the signature.""" - super().__init__(tag=SegTag.SIG, version=version) - self._data = bytearray() if data is None else bytearray(data) - - def __len__(self) -> int: - return len(self._data) - - def __getitem__(self, key: int) -> int: - return self._data[key] - - def __setitem__(self, key: int, value: int) -> None: - self._data[key] = value - - def __iter__(self) -> Iterator[int]: - return self._data.__iter__() - - def __repr__(self) -> str: - return f"Signature > 4}.{self.version & 0xF}, Size: {len(self._data)}>" - - def __str__(self) -> str: - """String representation of the signature.""" - msg = "-" * 60 + "\n" - msg += f"Signature (Ver: {self.version >> 4:X}.{self.version & 0xF:X}, Size: {len(self._data)})\n" - msg += "-" * 60 + "\n" - return msg - - @property - def data(self) -> bytes: - """Signature data.""" - return bytes(self._data) - - @data.setter - def data(self, value: Union[bytes, bytearray]) -> None: - """Signature data.""" - self._data = bytearray(value) - - def export(self) -> bytes: - """Export.""" - self._header.length = self.size - raw_data = self._header.export() - raw_data += self.data - return raw_data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse.""" - header = Header.parse(data, SegTag.SIG.tag) - return cls(header.param, data[Header.SIZE : header.length]) - - -class MAC(BaseSecretClass): - """Structure that holds initial parameter for AES encryption/decryption. - - - nonce - initialization vector for AEAD AES128 decryption - - mac - message authentication code to verify the decryption was successful - """ - - # AES block size in bytes; This also match size of the MAC and - AES128_BLK_LEN = 16 - - def __init__( - self, - version: int = 0x40, - nonce_len: int = 0, - mac_len: int = AES128_BLK_LEN, - data: Optional[bytes] = None, - ): - """Constructor. - - :param version: format version, should be 0x4x - :param nonce_len: number of NONCE bytes - :param mac_len: number of MAC bytes - :param data: nonce and mac bytes joined together - """ - super().__init__(tag=SegTag.MAC, version=version) - self.nonce_len = nonce_len - self.mac_len = mac_len - self._data: bytes = bytes() if data is None else bytes(data) - if data: - self._validate_data() - - @property - def size(self) -> int: - """Size of binary representation in bytes.""" - return Header.SIZE + 4 + self.nonce_len + self.mac_len - - def _validate_data(self) -> None: - """Validates the data. - - :raises SPSDKError: If data length does not match with parameters - """ - if len(self.data) != self.nonce_len + self.mac_len: - raise SPSDKError( - f"length of data ({len(self.data)}) does not match with " - f"nonce_bytes({self.nonce_len})+mac_bytes({self.mac_len})" - ) - - @property - def data(self) -> bytes: - """NONCE and MAC bytes joined together.""" - return self._data - - @data.setter - def data(self, value: bytes) -> None: - """Setter. - - :param value: NONCE and MAC bytes joined together - """ - self._data = value - self._validate_data() - - @property - def nonce(self) -> bytes: - """NONCE bytes for the encryption/decryption.""" - self._validate_data() - return self._data[0 : self.nonce_len] - - @property - def mac(self) -> bytes: - """MAC bytes for the encryption/decryption.""" - self._validate_data() - return self._data[self.nonce_len : self.nonce_len + self.mac_len] - - def update_aead_encryption_params(self, nonce: bytes, mac: bytes) -> None: - """Update AEAD encryption parameters for encrypted image. - - :param nonce: initialization vector, length depends on image size, - :param mac: message authentication code used to authenticate decrypted data, 16 bytes - :raises SPSDKError: If incorrect length of mac - :raises SPSDKError: If incorrect length of nonce - :raises SPSDKError: If incorrect number of MAC bytes" - """ - if len(mac) != MAC.AES128_BLK_LEN: - raise SPSDKError("Incorrect length of mac") - if len(nonce) < 11 or len(nonce) > 13: - raise SPSDKError("Incorrect length of nonce") - self.nonce_len = len(nonce) - if self.mac_len != MAC.AES128_BLK_LEN: - raise SPSDKError("Incorrect number of MAC bytes") - self.data = nonce + mac - - def __len__(self) -> int: - return len(self._data) - - def __repr__(self) -> str: - return ( - f"MAC " - ) - - def __str__(self) -> str: - """Text info about the instance.""" - msg = "-" * 60 + "\n" - msg += f"MAC (Version: {self.version >> 4:X}.{self.version & 0xF:X})\n" - msg += "-" * 60 + "\n" - msg += f"Nonce Len: {self.nonce_len} Bytes\n" - msg += f"MAC Len: {self.mac_len} Bytes\n" - msg += f"[{self._data.hex()}]\n" - return msg - - def export(self) -> bytes: - """Export instance into binary form (serialization). - - :return: binary form - """ - self._validate_data() - self._header.length = self.size - raw_data = self._header.export() - raw_data += pack(">4B", 0, self.nonce_len, 0, self.mac_len) - raw_data += self.data - return raw_data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse binary data and creates the instance (deserialization). - - :param data: being parsed - :return: the instance - """ - header = Header.parse(data, SegTag.MAC.tag) - (_, nonce_bytes, _, mac_bytes) = unpack_from(">4B", data, Header.SIZE) - return cls( - header.param, - nonce_bytes, - mac_bytes, - data[Header.SIZE + 4 : header.length], - ) - - -class SRKException(SPSDKError): - """SRK table processing exceptions.""" - - -class NotImplementedSRKPublicKeyType(SRKException): - """This SRK public key algorithm is not yet implemented.""" - - -class NotImplementedSRKCertificate(SRKException): - """This SRK public key algorithm is not yet implemented.""" - - -class NotImplementedSRKItem(SRKException): - """This type of SRK table item is not implemented.""" - - -class SrkItem: - """Base class for items in the SRK Table, see `SrkTable` class. - - We do not inherit from BaseClass because our header parameter - is an algorithm identifier, not a version number. - """ - - def __eq__(self, other: Any) -> bool: - return isinstance(other, self.__class__) and vars(other) == vars(self) - - def __ne__(self, obj: Any) -> bool: - return not self.__eq__(obj) - - @property - def size(self) -> int: - """Size of the exported binary data. - - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - def __str__(self) -> str: - """Description about the instance. - - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - def sha256(self) -> bytes: - """Export SHA256 hash of the original data. - - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - def hashed_entry(self) -> "SrkItem": - """This SRK item should be replaced with an incomplete entry with its digest. - - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - def export(self) -> bytes: - """Serialization to binary form. - - :return: binary representation of the instance - :raises NotImplementedError: Derived class has to implement this method - """ - raise NotImplementedError("Derived class has to implement this method.") - - @classmethod - def parse(cls, data: bytes) -> Self: - """Pick up the right implementation of an SRK item. - - :param data: The bytes array of SRK segment - :return: SrkItem: One of the SrkItem subclasses - :raises NotImplementedSRKPublicKeyType: Unsupported key algorithm - :raises NotImplementedSRKItem: Unsupported tag - """ - header = Header.parse(data) - if header.tag == EnumSRK.KEY_PUBLIC: - if header.param == EnumAlgorithm.PKCS1: - return SrkItemRSA.parse(data) # type: ignore - elif header.param == EnumAlgorithm.ECDSA: - return SrkItemEcc.parse(data) # type: ignore - raise NotImplementedSRKPublicKeyType(f"{header.param}") - if header.tag == EnumSRK.KEY_HASH: - return SrkItemHash.parse(data) # type: ignore - raise NotImplementedSRKItem(f"TAG = {header.tag}, PARAM = {header.param}") - - @classmethod - def from_certificate(cls, cert: Certificate) -> "SrkItem": - """Pick up the right implementation of an SRK item.""" - assert isinstance(cert, Certificate) - try: - return SrkItemRSA.from_certificate(cert) - except SPSDKError: - pass - try: - return SrkItemEcc.from_certificate(cert) - except SPSDKError: - pass - raise NotImplementedSRKCertificate() - - -class SrkItemHash(SrkItem): - """Hashed stub of some public key. - - This is a valid entry of the SRK table, it represents - some public key of unknown algorithm. - Can only provide its hashed value of itself. - """ - - @property - def algorithm(self) -> int: - """Hashing algorithm used.""" - return self._header.param - - @property - def size(self) -> int: - """Size of an SRK item.""" - return self._header.length - - def __init__(self, algorithm: int, digest: bytes) -> None: - """Build the stub entry with public key hash only. - - :param algorithm: int: Hash algorithm, only SHA256 now - :param digest: bytes: Hash digest value - :raises SPSDKError: If incorrect algorithm - """ - if algorithm != EnumAlgorithm.SHA256: - raise SPSDKError("Incorrect algorithm") - self._header = Header(tag=EnumSRK.KEY_HASH.tag, param=algorithm) - self.digest = digest - self._header.length += len(digest) - - def __repr__(self) -> str: - return f"SRK Hash " - - def __str__(self) -> str: - """String representation of SrkItemHash.""" - msg = str() - msg += f"Hash algorithm: {EnumAlgorithm.from_tag(self._header.param)}\n" - msg += "Hash value:\n" - msg += hexdump_fmt(self.digest) - return msg - - def sha256(self) -> bytes: - """Export SHA256 hash of the original data.""" - return self.digest - - def hashed_entry(self) -> "SrkItemHash": - """This SRK item should be replaced with an incomplete entry with its digest.""" - return self - - def export(self) -> bytes: - """Export.""" - data = self._header.export() - data += self.digest - return data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse SRK table item data. - - :param data: The bytes array of SRK segment - :return: SrkItemHash: SrkItemHash object - :raises NotImplementedSRKItem: Unknown tag - """ - header = Header.parse(data, EnumSRK.KEY_HASH.tag) - rest = data[header.SIZE :] - if header.param == EnumAlgorithm.SHA256: - digest = rest[: sha256().digest_size] - return cls(EnumAlgorithm.SHA256.tag, digest) - raise NotImplementedSRKItem(f"TAG = {header.tag}, PARAM = {header.param}") - - -class SrkItemRSA(SrkItem): - """RSA public key in SRK Table, see `SrkTable` class.""" - - @property - def algorithm(self) -> int: - """Algorithm.""" - return self._header.param - - @property - def size(self) -> int: - """Size of an SRK item.""" - return self._header.length - - @property - def flag(self) -> int: - """Flag.""" - return self._flag - - @flag.setter - def flag(self, value: int) -> None: - if value not in (0, 0x80): - raise SPSDKError("Incorrect flag") - self._flag = value - - @property - def key_length(self) -> int: - """Key length of Item in SRK Table.""" - return len(self.modulus) * 8 - - def __init__(self, modulus: bytes, exponent: bytes, flag: int = 0) -> None: - """Initialize the srk table item.""" - assert isinstance(modulus, bytes) - assert isinstance(exponent, bytes) - self._header = Header(tag=EnumSRK.KEY_PUBLIC.tag, param=EnumAlgorithm.PKCS1.tag) - self.flag = flag - self.modulus = modulus - self.exponent = exponent - self._header.length += 8 + len(self.modulus) + len(self.exponent) - - def __repr__(self) -> str: - return ( - f"SRK " - ) - - def __str__(self) -> str: - """String representation of SrkItemRSA.""" - exp = int.from_bytes(self.exponent, Endianness.BIG.value) - return ( - f"Algorithm: {EnumAlgorithm.from_tag(self.algorithm)}\n" - f"Flag: 0x{self.flag:02X} {'(CA)' if self.flag == 0x80 else ''}\n" - f"Length: {self.key_length} bit\n" - "Modulus:\n" - f"{modulus_fmt(self.modulus)}\n" - f"Exponent: {exp} (0x{exp:X})\n" - ) - - def sha256(self) -> bytes: - """Export SHA256 hash of the data.""" - srk_data = self.export() - return sha256(srk_data).digest() - - def hashed_entry(self) -> "SrkItemHash": - """This SRK item should be replaced with an incomplete entry with its digest.""" - return SrkItemHash(EnumAlgorithm.SHA256.tag, self.sha256()) - - def export(self) -> bytes: - """Export.""" - data = self._header.export() - data += pack(">4B2H", 0, 0, 0, self.flag, len(self.modulus), len(self.exponent)) - data += bytes(self.modulus) - data += bytes(self.exponent) - return data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse SRK table item data. - - :param data: The bytes array of SRK segment - :return: SrkItemRSA: SrkItemRSA object - """ - Header.parse(data, EnumSRK.KEY_PUBLIC.tag) - (flag, modulus_len, exponent_len) = unpack_from(">B2H", data, Header.SIZE + 3) - offset = 5 + Header.SIZE + 3 - modulus = data[offset : offset + modulus_len] - offset += modulus_len - exponent = data[offset : offset + exponent_len] - return cls(modulus, exponent, flag) - - @classmethod - def from_certificate(cls, cert: Certificate) -> "SrkItemRSA": - """Create SRKItemRSA from certificate.""" - assert isinstance(cert, Certificate) - flag = 0 - try: - key_usage = cert.extensions.get_extension_for_class(SPSDKKeyUsage) - assert isinstance(key_usage.value, SPSDKKeyUsage) - if key_usage.value.key_cert_sign: - flag = 0x80 - except ExtensionNotFound: - pass - try: - public_key = cert.get_public_key() - if not isinstance(public_key, PublicKeyRsa): - raise SPSDKError("Not an RSA key") - # get modulus and exponent of public key since we are RSA - modulus_len = math.ceil(public_key.n.bit_length() / 8) - exponent_len = math.ceil(public_key.e.bit_length() / 8) - modulus = public_key.n.to_bytes(modulus_len, Endianness.BIG.value) - exponent = public_key.e.to_bytes(exponent_len, Endianness.BIG.value) - - return cls(modulus, exponent, flag) - except SPSDKError as exc: - raise NotImplementedSRKCertificate() from exc - - -class SrkItemEcc(SrkItem): - """ECC public key in SRK Table, see `SrkTable` class.""" - - ECC_KEY_TYPE = { - EccCurve.SECP256R1: 0x4B, - EccCurve.SECP384R1: 0x4D, - EccCurve.SECP521R1: 0x4E, - } - - @property - def algorithm(self) -> int: - """Algorithm.""" - return self._header.param - - @property - def size(self) -> int: - """Size of an SRK item.""" - return self._header.length - - @property - def flag(self) -> int: - """Flag.""" - return self._flag - - @flag.setter - def flag(self, value: int) -> None: - # Check - if value not in (0, 0x80): - raise SPSDKError("Incorrect flag") - self._flag = value - - def __init__( - self, key_size: int, x_coordinate: int, y_coordinate: int, flag: int = 0 - ) -> None: - """Initialize the srk table item.""" - self._header = Header(tag=EnumSRK.KEY_PUBLIC.tag, param=EnumAlgorithm.ECDSA.tag) - self.x_coordinate = x_coordinate - self.y_coordinate = y_coordinate - self.key_size = key_size - self.coordinate_size = math.ceil(key_size / 8) - self.flag = flag - self._header.length += ( - 8 - + len( - self.x_coordinate.to_bytes( - self.coordinate_size, byteorder=Endianness.BIG.value - ) - ) - + len( - self.y_coordinate.to_bytes( - self.coordinate_size, byteorder=Endianness.BIG.value - ) - ) - ) - - def __repr__(self) -> str: - return ( - f"SRK " - ) - - def __str__(self) -> str: - """String representation of SrkItemEcc.""" - return ( - f"Algorithm: {EnumAlgorithm.from_tag(self.algorithm)}\n" - f"Flag: 0x{self.flag:02X} {'(CA)' if self.flag == 0x80 else ''}\n" - f"Key size: {self.key_size} bit\n" - f"X coordinate: {self.x_coordinate}\n" - f"Y coordinate: {self.y_coordinate}\n" - ) - - def sha256(self) -> bytes: - """Export SHA256 hash of the data.""" - srk_data = self.export() - return sha256(srk_data).digest() - - def hashed_entry(self) -> "SrkItemHash": - """This SRK item should be replaced with an incomplete entry with its digest.""" - return SrkItemHash(EnumAlgorithm.SHA256.tag, self.sha256()) - - def export(self) -> bytes: - """Export.""" - data = self._header.export() - curve_id = self.ECC_KEY_TYPE[get_ecc_curve(self.key_size // 8)] - data += pack( - ">8B", - 0, - 0, - 0, - self.flag, - curve_id, - 0, - self.key_size >> 8 & 0xFF, - self.key_size & 0xFF, - ) - data += self.x_coordinate.to_bytes( - self.coordinate_size, byteorder=Endianness.BIG.value - ) - data += self.y_coordinate.to_bytes( - self.coordinate_size, byteorder=Endianness.BIG.value - ) - return data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse SRK table item data. - - :param data: The bytes array of SRK segment - :return: SrkItemEcc: SrkItemEcc object - """ - Header.parse(data, EnumSRK.KEY_PUBLIC.tag) - (flag, curve_id, _, key_size) = unpack_from(">3BH", data, Header.SIZE + 3) - if curve_id not in list(cls.ECC_KEY_TYPE.values()): - raise SPSDKError(f"Unknown curve with id {curve_id}") - offset = 5 + Header.SIZE + 3 - coordinate_size = math.ceil(key_size / 8) - x_coordinate = data[offset : offset + coordinate_size] - offset += coordinate_size - y_coordinate = data[offset : offset + coordinate_size] - return cls( - key_size, - int.from_bytes(x_coordinate, Endianness.BIG.value), - int.from_bytes(y_coordinate, Endianness.BIG.value), - flag, - ) - - @classmethod - def from_certificate(cls, cert: Certificate) -> "SrkItemEcc": - """Create SrkItemEcc from certificate.""" - flag = 0 - try: - key_usage = cert.extensions.get_extension_for_class(SPSDKKeyUsage) - assert isinstance(key_usage.value, SPSDKKeyUsage) - if key_usage.value.key_cert_sign: - flag = 0x80 - except ExtensionNotFound: - pass - - try: - public_key = cert.get_public_key() - if not isinstance(public_key, PublicKeyEcc): - raise SPSDKError("Not an ECC key") - return cls(public_key.key_size, public_key.x, public_key.y, flag) - except SPSDKError as exc: - raise NotImplementedSRKCertificate() from exc - - -class SrkTable(BaseSecretClass): - """SRK table.""" - - @property - def size(self) -> int: - """Size of SRK table.""" - size = Header.SIZE - for key in self._keys: - size += key.size - return size - - def __init__(self, version: int = 0x40) -> None: - """Initialize SRT Table. - - :param version: format version - """ - super().__init__(tag=SegTag.CRT, version=version) - self._keys: List[SrkItem] = [] - - def __len__(self) -> int: - return len(self._keys) - - def __getitem__(self, key: int) -> SrkItem: - return self._keys[key] - - def __setitem__(self, key: int, value: SrkItem) -> None: - assert isinstance(value, SrkItem) - self._keys[key] = value - - def __iter__(self) -> Iterator[SrkItem]: - return self._keys.__iter__() - - def __repr__(self) -> str: - return ( - f"SRK_Table " - ) - - def __str__(self) -> str: - """Text info about the instance.""" - msg = "-" * 60 + "\n" - msg += ( - f"SRK Table (Version: {self.version_major:X}.{self.version_minor:X}, " - f"#Keys: {len(self._keys)})\n" - ) - msg += "-" * 60 + "\n" - for i, srk in enumerate(self._keys): - msg += f"SRK Key Index: {i} \n" - msg += str(srk) - msg += "\n" - return msg - - def append(self, srk: SrkItem) -> None: - """Add SRK item. - - :param srk: item to be added - """ - self._keys.append(srk) - - def get_fuse(self, index: int) -> int: - """Retrieve fuse value for the given index. - - :param index: of the fuse, 0-7 - :return: value of the specified fuse; the value is in format, that cane be used as parameter for SDP - `efuse_read_once` or `efuse_write_once` - :raises SPSDKError: If incorrect index of the fuse - :raises SPSDKError: If incorrect length of SRK items - """ - if index < 0 or index >= 8: - raise SPSDKError("Incorrect index of the fuse") - int_data = self.export_fuses()[index * 4 : (1 + index) * 4] - if len(int_data) != 4: - raise SPSDKError("Incorrect length of SRK items") - return unpack(" bytes: - """SRK items in binary form, see `SRK_fuses.bin` file.""" - data = b"" - for srk in self._keys: - data += srk.sha256() - return sha256(data).digest() - - def export(self) -> bytes: - """Export into binary form (serialization). - - :return: binary representation of the instance - """ - self._header.length = self.size - raw_data = self._header.export() - for srk in self._keys: - raw_data += srk.export() - return raw_data - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse of SRK table.""" - header = Header.parse(data, SegTag.CRT.tag) - offset = Header.SIZE - obj = cls(header.param) - obj._header.length = header.length # pylint: disable=protected-access - length = header.length - Header.SIZE - while length > 0: - srk = SrkItem.parse(data[offset:]) - offset += srk.size - length -= srk.size - obj.append(srk) - return obj diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/__init__.py b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/__init__.py index f6af88e5..b74e48fb 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/__init__.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/__init__.py @@ -7,22 +7,4 @@ # SPDX-License-Identifier: BSD-3-Clause """Module implementing communication with the MCU Bootloader.""" - -from typing import Union - -from .interfaces.buspal import MbootBuspalI2CInterface, MbootBuspalSPIInterface -from .interfaces.sdio import MbootSdioInterface -from .interfaces.uart import MbootUARTInterface -from .interfaces.usb import MbootUSBInterface -from .interfaces.usbsio import MbootUsbSioI2CInterface, MbootUsbSioSPIInterface -from .mcuboot import McuBoot - -MbootDeviceTypes = Union[ - MbootBuspalI2CInterface, - MbootBuspalSPIInterface, - MbootSdioInterface, - MbootUARTInterface, - MbootUSBInterface, - MbootUsbSioI2CInterface, - MbootUsbSioSPIInterface, -] +# diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/buspal.py b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/buspal.py deleted file mode 100644 index 92a7e93b..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/buspal.py +++ /dev/null @@ -1,566 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Buspal Mboot device implementation.""" -import datetime -import logging -import struct -import time -from dataclasses import dataclass -from enum import Enum -from typing import Any, Dict, List, Optional, Tuple - -from serial import SerialException -from serial.tools.list_ports import comports -from typing_extensions import Self - -from ...exceptions import SPSDKError -from ...mboot.exceptions import McuBootConnectionError, McuBootDataAbortError -from ...mboot.protocol.serial_protocol import FPType, MbootSerialProtocol, to_int -from ...utils.interfaces.device.serial_device import SerialDevice - -logger = logging.getLogger(__name__) - - -@dataclass -class ScanArgs: - """Scan arguments dataclass.""" - - port: Optional[str] - props: Optional[List[str]] - - @classmethod - def parse(cls, params: str, extra_params: Optional[str] = None) -> Self: - """Parse given scanning parameters and extra parameters into ScanArgs class. - - :param params: Parameters as a string - :param extra_params: Optional extra parameters as a string - """ - props = [] - if extra_params: - props = extra_params.split(",") - target = props.pop(0) - if target not in ["spi", "i2c"]: - raise SPSDKError(f"Target must be either 'spi' or 'ic2', not {target}") - port_parts = params.split(",") - return cls(port=port_parts.pop(0), props=props) - - -class SpiModeCommand(Enum): - """Spi mode commands.""" - - exit = 0x00 # 00000000 - Exit to bit bang mode - version = 0x01 # 00000001 - Enter raw SPI mode, display version string - chip_select = 0x02 # 0000001x - CS high (1) or low (0) - sniff = 0x0C # 000011XX - Sniff SPI traffic when CS low(10)/all(01) - bulk_transfer = ( - 0x10 # 0001xxxx - Bulk SPI transfer, send/read 1-16 bytes (0=1byte!) - ) - config_periph = ( - 0x40 # 0100wxyz - Configure peripherals w=power, x=pull-ups, y=AUX, z=CS - ) - set_speed = 0x60 # 01100xxx - SPI speed - config_spi = ( - 0x80 # 1000wxyz - SPI config, w=HiZ/3.3v, x=CKP idle, y=CKE edge, z=SMP sample - ) - write_then_read = 0x04 # 00000100 - Write then read extended command - - -# pylint: disable=invalid-name -class SpiConfigShift(Enum): - """Spi configuration shifts for the mask.""" - - direction = 0 - phase = 1 - polarity = 2 - - -# pylint: disable=invalid-name -class SpiClockPolarity(Enum): - """SPI clock polarity configuration.""" - - active_high = 0 # Active-high SPI clock (idles low). - active_low = 1 # Active-low SPI clock (idles high). - - -# pylint: disable=invalid-name -class SpiClockPhase(Enum): - """SPI clock phase configuration.""" - - # First edge on SPSCK occurs at the middle of the first cycle of a data transfer. - first_edge = 0 - # First edge on SPSCK occurs at the start of the first cycle of a data transfer. - second_edge = 1 - - -# pylint: disable=invalid-name -class SpiShiftDirection(Enum): - """SPI clock phase configuration.""" - - msb_first = 0 # Data transfers start with most significant bit. - lsb_first = 1 # Data transfers start with least significant bit. - - -class SpiConfiguration: - """Dataclass to store SPI configuration.""" - - speed: int - polarity: SpiClockPolarity - phase: SpiClockPhase - direction: SpiShiftDirection - - -# pylint: disable=invalid-name -class BBConstants(Enum): - """Constants.""" - - reset_count = 20 # Max number of nulls to send to enter BBIO mode - response_ok = 0x01 # Successful command response - bulk_transfer_max = 4096 # Max number of bytes per bulk transfer - packet_timeout_ms = 10 # Packet timeout in milliseconds - - -class Response(str, Enum): - """Response to enter bit bang mode.""" - - BITBANG = "BBIO1" - SPI = "SPI1" - I2C = "I2C1" - - -class BuspalMode(Enum): - """Bit Bang mode command.""" - - RESET = 0x00 # Reset, responds "BBIO1" - SPI = 0x01 # Enter binary SPI mode, responds "SPI1" - I2C = 0x02 # Enter binary I2C mode, responds "I2C1" - - -MODE_COMMANDS_RESPONSES = { - BuspalMode.RESET: Response.BITBANG, - BuspalMode.SPI: Response.SPI, - BuspalMode.I2C: Response.I2C, -} - - -class MbootBuspalProtocol(MbootSerialProtocol): - """Mboot Serial protocol.""" - - default_baudrate = 57600 - default_timeout = 5000 - device: SerialDevice - mode: BuspalMode - - def __init__(self, device: SerialDevice) -> None: - """Initialize the MbootBuspalProtocol object. - - :param device: The device instance - """ - super().__init__(device) - - def open(self) -> None: - """Open the interface.""" - self.device.open() - # reset first, send bit-bang command - self._enter_mode(BuspalMode.RESET) - logger.debug("Entered BB mode") - self._enter_mode(self.mode) - - @classmethod - def scan( - cls, - port: Optional[str] = None, - props: Optional[List[str]] = None, - timeout: Optional[int] = None, - ) -> List[SerialDevice]: - """Scan connected serial ports and set BUSPAL properties. - - Returns list of serial ports with devices that respond to BUSPAL communication protocol. - If 'port' is specified, only that serial port is checked - If no devices are found, return an empty list. - - :param port: name of preferred serial port, defaults to None - :param timeout: timeout in milliseconds - :param props: buspal target properties - :return: list of available interfaces - """ - timeout = timeout or cls.default_timeout - if port: - device = cls._check_port_buspal(port, timeout, props) - devices = [device] if device else [] - else: - all_ports = [ - cls._check_port_buspal(comport.device, timeout, props) - for comport in comports(include_links=True) - ] - devices = list(filter(None, all_ports)) - return devices - - @classmethod - def _check_port_buspal( - cls, port: str, timeout: int, props: Optional[List[str]] = None - ) -> Optional[SerialDevice]: - """Check if device on comport 'port' can connect using BUSPAL communication protocol. - - :param port: name of port to check - :param timeout: timeout in milliseconds - :param props: buspal settings - :return: None if device doesn't respond to PING, instance of Interface if it does - """ - props = props if props is not None else [] - try: - device = SerialDevice( - port=port, timeout=timeout, baudrate=cls.default_baudrate - ) - interface = cls(device) - interface.open() - interface._configure(props) - interface._ping() - return device - except (AssertionError, SerialException, McuBootConnectionError) as e: - logger.error(str(e)) - return None - - def _send_frame(self, frame: bytes, wait_for_ack: bool = True) -> None: - """Send frame method to be implemented by child class.""" - raise NotImplementedError() - - def _read(self, size: int, timeout: Optional[int] = None) -> bytes: - """Implementation done by child class.""" - raise NotImplementedError() - - def _configure(self, props: List[str]) -> None: - """Configure the BUSPAL interface. - - :param props: buspal settings - """ - raise NotImplementedError() - - def _enter_mode(self, mode: BuspalMode) -> None: - """Enter BUSPAL mode. - - :param mode: buspal mode - """ - response = MODE_COMMANDS_RESPONSES[mode] - self._send_command_check_response( - bytes([mode.value]), bytes(response.value.encode("utf-8")) - ) - - def _send_command_check_response(self, command: bytes, response: bytes) -> None: - """Send a command and check if expected response is received. - - :param command: command to send - :param response: expected response - """ - self.device.write(command) - data_recvd = self.device.read(len(response)) - format_received = " ".join(hex(x) for x in data_recvd) - format_expected = " ".join(hex(x) for x in response) - assert ( - format_received == format_expected - ), f"Received data '{format_received}' but expected '{format_expected}'" - - def _read_frame_header( - self, expected_frame_type: Optional[FPType] = None - ) -> Tuple[int, int]: - """Read frame header and frame type. Return them as tuple of integers. - - :param expected_frame_type: Check if the frame_type is exactly as expected - :return: Tuple of integers representing frame header and frame type - :raises AssertionError: Unexpected frame header or frame type (if specified) - :raises McuBootDataAbortError: Abort frame received - """ - header = None - time_start = datetime.datetime.now() - time_end = time_start + datetime.timedelta(milliseconds=self.device.timeout) - - # read uart until start byte is equal to FRAME_START_BYTE, max. 'retry_count' times - while header != self.FRAME_START_BYTE and datetime.datetime.now() < time_end: - header = to_int(self._read(1)) - if header == FPType.ABORT: - raise McuBootDataAbortError() - if header != self.FRAME_START_BYTE: - time.sleep(BBConstants.packet_timeout_ms.value / 1000) - assert ( - header == self.FRAME_START_BYTE - ), f"Received invalid frame header '{header:#X}' expected '{self.FRAME_START_BYTE:#X}'" - - frame_type = to_int(self._read(1)) - - if frame_type == FPType.ABORT: - raise McuBootDataAbortError() - return header, frame_type - - -class MbootBuspalSPIInterface(MbootBuspalProtocol): - """BUSPAL SPI interface.""" - - TARGET_SETTINGS = ["speed", "polarity", "phase", "direction"] - - HDR_FRAME_RETRY_CNT = 3 - ACK_WAIT_DELAY = 0.01 # in seconds - device: SerialDevice - identifier = "buspal_spi" - - def __init__(self, device: SerialDevice): - """Initialize the BUSPAL SPI interface. - - :param port: name of the serial port, defaults to None - :param timeout: read/write timeout in milliseconds - """ - self.mode = BuspalMode.SPI - super().__init__(device) - - @classmethod - def scan_from_args( - cls, - params: str, - timeout: int, - extra_params: Optional[str] = None, - ) -> List[Self]: - """Scan connected Buspal devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Timeout for the scan - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params, extra_params) - devices = cls.scan(port=scan_args.port, props=scan_args.props, timeout=timeout) - interfaces = [] - for device in devices: - interfaces.append(cls(device)) - return interfaces - - def _configure(self, props: List[str]) -> None: - """Configure the BUSPAL SPI interface. - - :param props: buspal settings - """ - spi_props: Dict[str, Any] = dict(zip(self.TARGET_SETTINGS, props)) - - speed = int(spi_props.get("speed", 100)) - polarity = SpiClockPolarity( - spi_props.get("polarity", SpiClockPolarity.active_low) - ) - phase = SpiClockPhase(spi_props.get("phase", SpiClockPhase.second_edge)) - direction = SpiShiftDirection( - spi_props.get("direction", SpiShiftDirection.msb_first) - ) - - # set SPI config - logger.debug("Set SPI config") - spi_data = polarity.value << SpiConfigShift.polarity.value - spi_data |= phase.value << SpiConfigShift.phase.value - spi_data |= direction.value << SpiConfigShift.direction.value - spi_data |= SpiModeCommand.config_spi.value - self._send_command_check_response( - bytes([spi_data]), bytes([BBConstants.response_ok.value]) - ) - - # set SPI speed - logger.debug(f"Set SPI speed to {speed}bps") - spi_speed = struct.pack(" None: - """Send data to BUSPAL I2C device. - - :param data: Data to send - """ - self._send_frame_retry(data, wait_for_ack, self.HDR_FRAME_RETRY_CNT) - - def _send_frame_retry( - self, - data: bytes, - wait_for_ack: bool = True, - retry_cnt: int = HDR_FRAME_RETRY_CNT, - ) -> None: - """Send a frame to BUSPAL SPI device. - - :param data: Data to send - :param wait_for_ack: Wait for ACK frame from device, defaults to True - :param retry_cnt: Number of retry in case the header frame is incorrect - :raises AssertionError: Unexpected frame header or frame type (if specified) - """ - size = min(len(data), BBConstants.bulk_transfer_max.value) - command = struct.pack(" 0: - logger.error( - f"{error} (retry {self.HDR_FRAME_RETRY_CNT-retry_cnt+1}/{self.HDR_FRAME_RETRY_CNT})" - ) - retry_cnt -= 1 - self._send_frame_retry(data, wait_for_ack, retry_cnt) - else: - raise SPSDKError( - "Failed retrying reading the SPI header frame" - ) from error - - def _read(self, size: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount of bytes from BUSPAL SPI device. - - :return: Data read from the device - """ - size = min(size, BBConstants.bulk_transfer_max.value) - command = struct.pack(" List[Self]: - """Scan connected Buspal devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Timeout for the scan - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params, extra_params) - devices = cls.scan(port=scan_args.port, props=scan_args.props, timeout=timeout) - interfaces = [] - for device in devices: - interfaces.append(cls(device)) - return interfaces - - def _configure(self, props: List[str]) -> None: - """Initialize the BUSPAL I2C interface. - - :param props: buspal settings - """ - i2c_props: Dict[str, Any] = dict(zip(self.TARGET_SETTINGS, props)) - - # get I2C configuration values, use default values if settings are not defined in input string) - speed = int(i2c_props.get("speed", 100)) - address = int(i2c_props.get("address", 0x10)) - - # set I2C address - logger.debug(f"Set I2C address to {address}") - i2c_data = struct.pack(" None: - """Send data to BUSPAL I2C device. - - :param data: Data to send - """ - self._send_frame_retry(data, wait_for_ack, self.HDR_FRAME_RETRY_CNT) - - def _send_frame_retry( - self, - data: bytes, - wait_for_ack: bool = True, - retry_cnt: int = HDR_FRAME_RETRY_CNT, - ) -> None: - """Send data to BUSPAL I2C device. - - :param data: Data to send - :param wait_for_ack: Wait for ACK frame from device, defaults to True - :param retry_cnt: Number of retry in case the header frame is incorrect - :raises AssertionError: Unexpected frame header or frame type (if specified) - """ - retry_cnt = self.HDR_FRAME_RETRY_CNT - size = min(len(data), BBConstants.bulk_transfer_max.value) - command = struct.pack(" 0: - logger.error( - f"{error} (retry {self.HDR_FRAME_RETRY_CNT-retry_cnt+1}/{self.HDR_FRAME_RETRY_CNT})" - ) - retry_cnt -= 1 - self._send_frame_retry(data, wait_for_ack, retry_cnt) - else: - raise SPSDKError( - "Failed retrying reading the I2C header frame" - ) from error - - def _read(self, size: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount of bytes from BUSPAL I2C device. - - :return: Data read from the device - """ - size = min(size, BBConstants.bulk_transfer_max.value) - command = struct.pack(" Self: - """Parse given scanning parameters into ScanArgs class. - - :param params: Parameters as a string - """ - return cls(device_path=params) - - -class MbootSdioInterface(MbootSerialProtocol): - """Sdio interface.""" - - identifier = "sdio" - device: SdioDevice - sdio_devices = SDIO_DEVICES - - def __init__(self, device: SdioDevice) -> None: - """Initialize the MbootSdioInterface object. - - :param device: The device instance - """ - super().__init__(device=device) - - @property - def name(self) -> str: - """Get the name of the device. - - :return: Name of the device. - """ - assert isinstance(self.device, SdioDevice) - for name, value in self.sdio_devices.items(): - if value[0] == self.device.vid and value[1] == self.device.pid: - return name - return "Unknown" - - @classmethod - def scan_from_args( - cls, - params: str, - timeout: int, - extra_params: Optional[str] = None, - ) -> List[Self]: - """Scan connected USB devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Interface timeout - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params) - interfaces = cls.scan(device_path=scan_args.device_path, timeout=timeout) - return interfaces - - @classmethod - def scan( - cls, - device_path: str, - timeout: Optional[int] = None, - ) -> List[Self]: - """Scan connected SDIO devices. - - :param device_path: device path string - :param timeout: Interface timeout - :return: matched SDIO device - """ - devices = SdioDevice.scan(device_path=device_path, timeout=timeout) - return [cls(device) for device in devices] - - def open(self) -> None: - """Open the interface.""" - self.device.open() - - def read(self, length: Optional[int] = None) -> Union[CmdResponse, bytes]: - """Read data on the IN endpoint associated to the HID interface. - - :return: Return CmdResponse object. - :raises McuBootConnectionError: Raises an error if device is not opened for reading - :raises McuBootConnectionError: Raises if device is not available - :raises McuBootDataAbortError: Raises if reading fails - :raises TimeoutError: When timeout occurs - """ - raw_data = self._read(1024) - if not raw_data: - logger.error("Cannot read from SDIO device") - raise TimeoutError() - - _, frame_type = self._parse_frame_header(raw_data) - _length, crc = struct.unpack_from(" Tuple[int, int]: - """Read frame header and frame type. Return them as tuple of integers. - - :param expected_frame_type: Check if the frame_type is exactly as expected - :return: Tuple of integers representing frame header and frame type - :raises McuBootDataAbortError: Target sens Data Abort frame - :raises McuBootConnectionError: Unexpected frame header or frame type (if specified) - :raises McuBootConnectionError: When received invalid ACK - """ - data = self._read(2) - return self._parse_frame_header(data, FPType.ACK) - - def _parse_frame_header( - self, frame: bytes, expected_frame_type: Optional[FPType] = None - ) -> Tuple[int, int]: - """Read frame header and frame type. Return them as tuple of integers. - - :param expected_frame_type: Check if the frame_type is exactly as expected - :return: Tuple of integers representing frame header and frame type - :raises McuBootDataAbortError: Target sens Data Abort frame - :raises McuBootConnectionError: Unexpected frame header or frame type (if specified) - :raises McuBootConnectionError: When received invalid ACK - """ - header, frame_type = struct.unpack_from(" Self: - """Parse given scanning parameters into ScanArgs class. - - :param params: Parameters as a string - """ - port_parts = params.split(",") - return cls( - port=port_parts.pop(0), - baudrate=int(port_parts.pop(), 0) if port_parts else None, - ) - - -class MbootUARTInterface(MbootSerialProtocol): - """UART interface.""" - - default_baudrate = 57600 - device: SerialDevice - identifier = "uart" - - def __init__(self, device: SerialDevice): - """Initialize the MbootUARTInterface object. - - :param device: The device instance - """ - assert isinstance(device, SerialDevice) - super().__init__(device=device) - - @classmethod - def scan_from_args( - cls, - params: str, - timeout: int, - extra_params: Optional[str] = None, - ) -> List[Self]: - """Scan connected UART devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Timeout for the scan - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params=params) - interfaces = cls.scan( - port=scan_args.port, - baudrate=scan_args.baudrate or cls.default_baudrate, - timeout=timeout, - ) - return interfaces - - @classmethod - def scan( - cls, - port: Optional[str] = None, - baudrate: Optional[int] = None, - timeout: Optional[int] = None, - ) -> List[Self]: - """Scan connected UART devices. - - Returns list of serial ports with devices that respond to PING command. - If 'port' is specified, only that serial port is checked - If no devices are found, return an empty list. - - :param port: name of preferred serial port, defaults to None - :param baudrate: speed of the UART interface, defaults to 56700 - :param timeout: timeout in milliseconds, defaults to 5000 - :return: list of interfaces responding to the PING command - """ - devices = SerialDevice.scan( - port=port, baudrate=baudrate or cls.default_baudrate, timeout=timeout - ) - interfaces = [] - for device in devices: - try: - interface = cls(device) - interface.open() - interface._ping() - interface.close() - interfaces.append(interface) - except Exception: - interface.close() - return interfaces diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/usbsio.py b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/usbsio.py deleted file mode 100644 index 41d3123d..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/interfaces/usbsio.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright (c) 2019-2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""USBSIO Mboot interface implementation.""" -from typing import List, Optional - -from typing_extensions import Self - -from ...mboot.protocol.serial_protocol import MbootSerialProtocol -from ...utils.interfaces.device.usbsio_device import ( - ScanArgs, - UsbSioI2CDevice, - UsbSioSPIDevice, -) - - -class MbootUsbSioI2CInterface(MbootSerialProtocol): - """USBSIO I2C interface.""" - - device: UsbSioI2CDevice - identifier = "usbsio_i2c" - - def __init__(self, device: UsbSioI2CDevice): - """Initialize the UsbSioI2CDevice object. - - :param device: The device instance - """ - super().__init__(device=device) - - @classmethod - def scan_from_args( - cls, - params: str, - timeout: int, - extra_params: Optional[str] = None, - ) -> List[Self]: - """Scan connected USBSIO devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Timeout for the scan - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params=params) - interfaces = cls.scan(config=scan_args.config, timeout=timeout) - return interfaces - - @classmethod - def scan(cls, config: Optional[str] = None, timeout: int = 5000) -> List[Self]: - """Scan connected USB-SIO bridge devices. - - :param config: Configuration string identifying spi or i2c SIO interface - and could filter out USB devices - :param timeout: Read timeout in milliseconds, defaults to 5000 - :return: List of interfaces - """ - devices = UsbSioI2CDevice.scan(config, timeout) - spi_devices = [x for x in devices if isinstance(x, UsbSioI2CDevice)] - return [cls(device) for device in spi_devices] - - -class MbootUsbSioSPIInterface(MbootSerialProtocol): - """USBSIO I2C interface.""" - - # START_NOT_READY may be 0x00 or 0xFF depending on the implementation - FRAME_START_NOT_READY_LIST = [0x00, 0xFF] - device: UsbSioSPIDevice - identifier = "usbsio_spi" - - def __init__(self, device: UsbSioSPIDevice) -> None: - """Initialize the UsbSioSPIDevice object. - - :param device: The device instance - """ - super().__init__(device) - - @classmethod - def scan_from_args( - cls, - params: str, - timeout: int, - extra_params: Optional[str] = None, - ) -> List[Self]: - """Scan connected USBSIO devices. - - :param params: Params as a configuration string - :param extra_params: Extra params configuration string - :param timeout: Timeout for the scan - :return: list of matching RawHid devices - """ - scan_args = ScanArgs.parse(params=params) - interfaces = cls.scan(config=scan_args.config, timeout=timeout) - return interfaces - - @classmethod - def scan(cls, config: Optional[str] = None, timeout: int = 5000) -> List[Self]: - """Scan connected USB-SIO bridge devices. - - :param config: Configuration string identifying spi or i2c SIO interface - and could filter out USB devices - :param timeout: Read timeout in milliseconds, defaults to 5000 - :return: List of interfaces - """ - devices = UsbSioSPIDevice.scan(config, timeout) - spi_devices = [x for x in devices if isinstance(x, UsbSioSPIDevice)] - return [cls(device) for device in spi_devices] diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/protocol/serial_protocol.py b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/protocol/serial_protocol.py deleted file mode 100644 index 49385825..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/protocol/serial_protocol.py +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2016-2018 Martin Olejar -# Copyright 2019-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Mboot serial implementation.""" -import logging -import struct -import time -from contextlib import contextmanager -from typing import Generator, NamedTuple, Optional, Tuple, Union - -from crcmod.predefined import mkPredefinedCrcFun -from typing_extensions import Self - -from ...exceptions import SPSDKAttributeError -from ...mboot.commands import CmdResponse, parse_cmd_response -from ...mboot.exceptions import McuBootConnectionError, McuBootDataAbortError -from ...mboot.protocol.base import MbootProtocolBase -from ...utils.interfaces.commands import CmdPacketBase -from ...utils.misc import Endianness, Timeout -from ...utils.spsdk_enum import SpsdkEnum - -logger = logging.getLogger(__name__) - - -class PingResponse(NamedTuple): - """Special type of response for Ping Command.""" - - version: int - options: int - crc: int - - @classmethod - def parse(cls, data: bytes) -> Self: - """Parse raw data into PingResponse object. - - :param data: bytes to be unpacked to PingResponse object - 4B version, 2B data, 2B CRC16 - :raises McuBootConnectionError: Received invalid ping response - :return: PingResponse - """ - try: - version, options, crc = struct.unpack(" int: - """Convert bytes into single integer. - - :param data: bytes to convert - :param little_endian: indicate byte ordering in data, defaults to True - :return: integer - """ - byte_order = Endianness.LITTLE if little_endian else Endianness.BIG - return int.from_bytes(data, byteorder=byte_order.value) - - -class MbootSerialProtocol(MbootProtocolBase): - """Mboot Serial protocol.""" - - FRAME_START_BYTE = 0x5A - FRAME_START_NOT_READY_LIST = [0x00] - PING_TIMEOUT_MS = 500 - MAX_PING_RESPONSE_DUMMY_BYTES = 50 - MAX_UART_OPEN_ATTEMPTS = 3 - protocol_version: int = 0 - options: int = 0 - - def open(self) -> None: - """Open the interface. - - :raises McuBootConnectionError: In any case of fail of UART open operation. - """ - for i in range(self.MAX_UART_OPEN_ATTEMPTS): - try: - self.device.open() - self._ping() - logger.debug(f"Interface opened after {i + 1} attempts.") - return - except TimeoutError as e: - # Closing may take up 30-40 seconds - self.close() - logger.debug(f"Timeout when pinging the device: {repr(e)}") - except McuBootConnectionError as e: - self.close() - logger.debug(f"Opening interface failed with: {repr(e)}") - except Exception as exc: - self.close() - raise McuBootConnectionError( - "UART Interface open operation fails." - ) from exc - raise McuBootConnectionError( - f"Cannot open UART interface after {self.MAX_UART_OPEN_ATTEMPTS} attempts." - ) - - def close(self) -> None: - """Close the interface.""" - self.device.close() - - @property - def is_opened(self) -> bool: - """Indicates whether interface is open.""" - return self.device.is_opened - - def write_data(self, data: bytes) -> None: - """Encapsulate data into frames and send them to device. - - :param data: Data to be sent - """ - frame = self._create_frame(data, FPType.DATA) - self._send_frame(frame) - - def write_command(self, packet: CmdPacketBase) -> None: - """Encapsulate command into frames and send them to device. - - :param packet: Command packet object to be sent - :raises SPSDKAttributeError: Command packed contains no data to be sent - """ - data = packet.to_bytes(padding=False) - if not data: - raise SPSDKAttributeError("Incorrect packet type") - frame = self._create_frame(data, FPType.CMD) - self._send_frame(frame) - - def read(self, length: Optional[int] = None) -> Union[CmdResponse, bytes]: - """Read data from device. - - :return: read data - :raises McuBootDataAbortError: Indicates data transmission abort - :raises McuBootConnectionError: When received invalid CRC - """ - _, frame_type = self._read_frame_header() - _length = to_int(self._read(2)) - crc = to_int(self._read(2)) - if not _length: - self._send_ack() - raise McuBootDataAbortError() - data = self._read(_length) - self._send_ack() - calculated_crc = self._calc_frame_crc(data, frame_type) - if crc != calculated_crc: - raise McuBootConnectionError("Received invalid CRC") - if frame_type == FPType.CMD: - return parse_cmd_response(data) - return data - - def _read(self, length: int, timeout: Optional[int] = None) -> bytes: - """Internal read, done mainly due BUSPAL, where this is overriden.""" - return self.device.read(length, timeout) - - def _send_ack(self) -> None: - """Send ACK command.""" - ack_frame = struct.pack(" None: - """Write frame to the device and wait for ack. - - :param data: Data to be send - """ - self.device.write(frame) - if wait_for_ack: - self._read_frame_header(FPType.ACK) - - def _create_frame(self, data: bytes, frame_type: FPType) -> bytes: - """Encapsulate data into frame.""" - crc = self._calc_frame_crc(data, frame_type.tag) - frame = struct.pack( - f" int: - """Calculate the CRC of a frame. - - :param data: frame data - :param frame_type: frame type - :return: calculated CRC - """ - crc_data = struct.pack( - f" int: - """Calculate CRC from the data. - - :param data: data to calculate CRC from - :return: calculated CRC - """ - crc_function = mkPredefinedCrcFun("xmodem") - return crc_function(data) - - def _read_frame_header( - self, expected_frame_type: Optional[FPType] = None - ) -> Tuple[int, int]: - """Read frame header and frame type. Return them as tuple of integers. - - :param expected_frame_type: Check if the frame_type is exactly as expected - :return: Tuple of integers representing frame header and frame type - :raises McuBootDataAbortError: Target sens Data Abort frame - :raises McuBootConnectionError: Unexpected frame header or frame type (if specified) - :raises McuBootConnectionError: When received invalid ACK - """ - assert isinstance(self.device.timeout, int) - timeout = Timeout(self.device.timeout, "ms") - while not timeout.overflow(): - header = to_int(self._read(1)) - if header not in self.FRAME_START_NOT_READY_LIST: - break - # This is workaround addressing SPI ISP issue on RT5/6xx when sometimes - # ACK frames and START BYTE frames are swapped, see SPSDK-1824 for more details - if header not in [self.FRAME_START_BYTE, FPType.ACK]: - raise McuBootConnectionError( - f"Received invalid frame header '{header:#X}' expected '{self.FRAME_START_BYTE:#X}'" - + "\nTry increasing the timeout, some operations might take longer" - ) - if header == FPType.ACK: - frame_type: int = header - else: - frame_type = to_int(self._read(1)) - if frame_type == FPType.ABORT: - raise McuBootDataAbortError() - if expected_frame_type: - if frame_type == self.FRAME_START_BYTE: - frame_type = header - if frame_type != expected_frame_type: - raise McuBootConnectionError( - f"received invalid ACK '{frame_type:#X}' expected '{expected_frame_type.tag:#X}'" - ) - return header, frame_type - - def _ping(self) -> None: - """Ping the target device, retrieve protocol version. - - :raises McuBootConnectionError: If the target device doesn't respond to ping - :raises McuBootConnectionError: If the start frame is not received - :raises McuBootConnectionError: If the header is invalid - :raises McuBootConnectionError: If the frame type is invalid - :raises McuBootConnectionError: If the ping response is not received - :raises McuBootConnectionError: If crc does not match - """ - with self.ping_timeout(timeout=self.PING_TIMEOUT_MS): - ping = struct.pack(" Generator[None, None, None]: - """Context manager for changing UART's timeout. - - :param timeout: New temporary timeout in milliseconds, defaults to PING_TIMEOUT_MS (500ms) - :return: Generator[None, None, None] - """ - assert isinstance(self.device.timeout, int) - context_timeout = min(timeout, self.device.timeout) - original_timeout = self.device.timeout - self.device.timeout = context_timeout - logger.debug(f"Setting timeout to {context_timeout} ms") - # driver needs to be reconfigured after timeout change, wait for a little while - time.sleep(0.005) - - yield - - self.device.timeout = original_timeout - logger.debug(f"Restoring timeout to {original_timeout} ms") - time.sleep(0.005) diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/scanner.py b/pynitrokey/trussed/bootloader/lpc55_upload/mboot/scanner.py deleted file mode 100644 index e7bee059..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/mboot/scanner.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Helper module used for scanning the existing devices.""" -from typing import List, Optional - -from ..exceptions import SPSDKError -from ..utils.interfaces.scanner_helper import InterfaceParams, parse_plugin_config -from .protocol.base import MbootProtocolBase - - -def get_mboot_interface( - port: Optional[str] = None, - usb: Optional[str] = None, - sdio: Optional[str] = None, - buspal: Optional[str] = None, - lpcusbsio: Optional[str] = None, - plugin: Optional[str] = None, - timeout: int = 5000, -) -> MbootProtocolBase: - """Get appropriate interface. - - 'port', 'usb', 'sdio', 'lpcusbsio' parameters are mutually exclusive; one of them is required. - - :param port: name and speed of the serial port (format: name[,speed]), defaults to None - :param usb: PID,VID of the USB interface, defaults to None - :param sdio: SDIO path of the SDIO interface, defaults to None - :param buspal: buspal interface settings, defaults to None - :param timeout: timeout in milliseconds - :param lpcusbsio: LPCUSBSIO spi or i2c config string - :param plugin: Additional plugin to be used - :return: Selected interface instance - :raises SPSDKError: Only one of the appropriate interfaces must be specified - :raises SPSDKError: When SPSDK-specific error occurs - """ - # check that one and only one interface is defined - interface_params: List[InterfaceParams] = [] - plugin_params = parse_plugin_config(plugin) if plugin else ("Unknown", "") - interface_params.extend( - [ - InterfaceParams(identifier="usb", is_defined=bool(usb), params=usb), - InterfaceParams( - identifier="uart", is_defined=bool(port and not buspal), params=port - ), - InterfaceParams( - identifier="buspal_spi", - is_defined=bool(port and buspal and "spi" in buspal), - params=port, - extra_params=buspal, - ), - InterfaceParams( - identifier="buspal_i2c", - is_defined=bool(port and buspal and "i2c" in buspal), - params=port, - extra_params=buspal, - ), - InterfaceParams( - identifier="usbsio_spi", - is_defined=bool(lpcusbsio and "spi" in lpcusbsio), - params=lpcusbsio, - ), - InterfaceParams( - identifier="usbsio_i2c", - is_defined=bool(lpcusbsio and "i2c" in lpcusbsio), - params=lpcusbsio, - ), - InterfaceParams(identifier="sdio", is_defined=bool(sdio), params=sdio), - InterfaceParams( - identifier=plugin_params[0], - is_defined=bool(plugin), - params=plugin_params[1], - ), - ] - ) - interface_params = [ifce for ifce in interface_params if ifce.is_defined] - if len(interface_params) == 0: - raise SPSDKError( - "One of '--port', '--usb', '--sdio', '--lpcusbsio' or '--plugin' must be specified." - ) - if len(interface_params) > 1: - raise SPSDKError( - "Only one of '--port', '--usb', '--sdio', '--lpcusbsio' or '--plugin must be specified." - ) - interface = MbootProtocolBase.get_interface(interface_params[0].identifier) - assert interface_params[0].params - devices = interface.scan_from_args( - params=interface_params[0].params, - extra_params=interface_params[0].extra_params, - timeout=timeout, - ) - if len(devices) == 0: - raise SPSDKError( - f"Selected '{interface_params[0].identifier}' device not found." - ) - if len(devices) > 1: - raise SPSDKError( - f"Multiple '{interface_params[0].identifier}' devices found: {len(devices)}" - ) - return devices[0] diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/sbfile/sb2/images.py b/pynitrokey/trussed/bootloader/lpc55_upload/sbfile/sb2/images.py index b0748dc6..fc3e7abf 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/sbfile/sb2/images.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/sbfile/sb2/images.py @@ -18,11 +18,7 @@ from ...crypto.hash import EnumHashAlgorithm, get_hash from ...crypto.hmac import hmac from ...crypto.rng import random_bytes -from ...crypto.signature_provider import ( - SignatureProvider, - get_signature_provider, - try_to_verify_public_key, -) +from ...crypto.signature_provider import SignatureProvider, get_signature_provider from ...crypto.symmetric import Counter, aes_key_unwrap, aes_key_wrap from ...exceptions import SPSDKError from ...sbfile.misc import SecBootBlckSize @@ -110,376 +106,6 @@ def timestamp(self) -> datetime: return self._timestamp -######################################################################################################################## -# Secure Boot Image Class (Version 2.0) -######################################################################################################################## -class BootImageV20(BaseClass): - """Boot Image V2.0 class.""" - - # Image specific data - # size of the MAC key - HEADER_MAC_SIZE = 32 - # AES encrypted DEK and MAC, including padding - DEK_MAC_SIZE = 32 + 32 + 16 - - KEY_BLOB_SIZE = 80 - - def __init__( - self, - signed: bool, - kek: bytes, - *sections: BootSectionV2, - product_version: str = "1.0.0", - component_version: str = "1.0.0", - build_number: int = 0, - advanced_params: SBV2xAdvancedParams = SBV2xAdvancedParams(), - ) -> None: - """Initialize Secure Boot Image V2.0. - - :param signed: True if image is signed, False otherwise - :param kek: key for wrapping DEK and MAC keys - :param product_version: The product version (default: 1.0.0) - :param component_version: The component version (default: 1.0.0) - :param build_number: The build number value (default: 0) - :param advanced_params: Advanced parameters for encryption of the SB file, use for tests only - :param sections: Boot sections - :raises SPSDKError: Invalid dek or mac - """ - self._kek = kek - # Set Flags value - self._signed = signed - self.signature_provider: Optional[SignatureProvider] = None - flags = 0x08 if self.signed else 0x04 - # Set private attributes - self._dek: bytes = advanced_params.dek - self._mac: bytes = advanced_params.mac - if ( - len(self._dek) != self.HEADER_MAC_SIZE - and len(self._mac) != self.HEADER_MAC_SIZE - ): # pragma: no cover # condition checked in SBV2xAdvancedParams constructor - raise SPSDKError("Invalid dek or mac") - self._header = ImageHeaderV2( - version="2.0", - product_version=product_version, - component_version=component_version, - build_number=build_number, - flags=flags, - nonce=advanced_params.nonce, - timestamp=advanced_params.timestamp, - ) - self._cert_section: Optional[CertSectionV2] = None - self._boot_sections: List[BootSectionV2] = [] - # Generate nonce - if self._header.nonce is None: - nonce = bytearray(random_bytes(16)) - # clear nonce bit at offsets 31 and 63 - nonce[9] &= 0x7F - nonce[13] &= 0x7F - self._header.nonce = bytes(nonce) - # Sections - for section in sections: - self.add_boot_section(section) - - @property - def header(self) -> ImageHeaderV2: - """Return image header.""" - return self._header - - @property - def dek(self) -> bytes: - """Data encryption key.""" - return self._dek - - @property - def mac(self) -> bytes: - """Message authentication code.""" - return self._mac - - @property - def kek(self) -> bytes: - """Return key for wrapping DEK and MAC keys.""" - return self._kek - - @property - def signed(self) -> bool: - """Check whether sb is signed + encrypted or only encrypted.""" - return self._signed - - @property - def cert_block(self) -> Optional[CertBlockV1]: - """Return certificate block; None if SB file not signed or block not assigned yet.""" - cert_sect = self._cert_section - if cert_sect is None: - return None - - return cert_sect.cert_block - - @cert_block.setter - def cert_block(self, value: Optional[CertBlockV1]) -> None: - """Setter. - - :param value: block to be assigned; None to remove previously assigned block - :raises SPSDKError: When certificate block is used when SB file is not signed - """ - if value is not None: - if not self.signed: - raise SPSDKError( - "Certificate block cannot be used unless SB file is signed" - ) - self._cert_section = CertSectionV2(value) if value else None - - @property - def cert_header_size(self) -> int: - """Return image raw size (not aligned) for certificate header.""" - size = ImageHeaderV2.SIZE + self.HEADER_MAC_SIZE + self.KEY_BLOB_SIZE - for boot_section in self._boot_sections: - size += boot_section.raw_size - return size - - @property - def raw_size_without_signature(self) -> int: - """Return image raw size without signature, used to calculate image blocks.""" - # Header, HMAC and KeyBlob - size = ImageHeaderV2.SIZE + self.HEADER_MAC_SIZE + self.KEY_BLOB_SIZE - # Certificates Section - if self.signed: - size += self.DEK_MAC_SIZE - cert_block = self.cert_block - if not cert_block: - raise SPSDKError("Certification block not present") - size += cert_block.raw_size - # Boot Sections - for boot_section in self._boot_sections: - size += boot_section.raw_size - return size - - @property - def raw_size(self) -> int: - """Return image raw size.""" - size = self.raw_size_without_signature - - if self.signed: - cert_block = self.cert_block - if ( - not cert_block - ): # pragma: no cover # already checked in raw_size_without_signature - raise SPSDKError("Certificate block not present") - size += cert_block.signature_size - - return size - - def __len__(self) -> int: - return len(self._boot_sections) - - def __getitem__(self, key: int) -> BootSectionV2: - return self._boot_sections[key] - - def __setitem__(self, key: int, value: BootSectionV2) -> None: - self._boot_sections[key] = value - - def __iter__(self) -> Iterator[BootSectionV2]: - return self._boot_sections.__iter__() - - def update(self) -> None: - """Update boot image.""" - if self._boot_sections: - self._header.first_boot_section_id = self._boot_sections[0].uid - # calculate first boot tag block - data_size = self._header.SIZE + self.HEADER_MAC_SIZE + self.KEY_BLOB_SIZE - if self._cert_section is not None: - data_size += self._cert_section.raw_size - self._header.first_boot_tag_block = SecBootBlckSize.to_num_blocks(data_size) - # ... - self._header.flags = 0x08 if self.signed else 0x04 - self._header.image_blocks = SecBootBlckSize.to_num_blocks( - self.raw_size_without_signature - ) - self._header.header_blocks = SecBootBlckSize.to_num_blocks(self._header.SIZE) - self._header.max_section_mac_count = 0 - if self.signed: - self._header.offset_to_certificate_block = ( - self._header.SIZE + self.HEADER_MAC_SIZE + self.KEY_BLOB_SIZE - ) - self._header.offset_to_certificate_block += ( - CmdHeader.SIZE + CertSectionV2.HMAC_SIZE * 2 - ) - self._header.max_section_mac_count = 1 - for boot_sect in self._boot_sections: - boot_sect.is_last = True # this is unified with elftosb - self._header.max_section_mac_count += boot_sect.hmac_count - # Update certificates block header - cert_blk = self.cert_block - if cert_blk is not None: - cert_blk.header.build_number = self._header.build_number - cert_blk.header.image_length = self.cert_header_size - - def __repr__(self) -> str: - return f"SB2.0, {'Signed' if self.signed else 'Plain'} " - - def __str__(self) -> str: - """Return text description of the instance.""" - self.update() - nfo = "\n" - nfo += ":::::::::::::::::::::::::::::::::: IMAGE HEADER ::::::::::::::::::::::::::::::::::::::\n" - nfo += str(self._header) - if self._cert_section is not None: - nfo += "::::::::::::::::::::::::::::::: CERTIFICATES BLOCK ::::::::::::::::::::::::::::::::::::\n" - nfo += str(self._cert_section) - nfo += "::::::::::::::::::::::::::::::::::: BOOT SECTIONS ::::::::::::::::::::::::::::::::::::\n" - for index, section in enumerate(self._boot_sections): - nfo += f"[ SECTION: {index} | UID: 0x{section.uid:08X} ]\n" - nfo += str(section) - return nfo - - def add_boot_section(self, section: BootSectionV2) -> None: - """Add new Boot section into image. - - :param section: Boot section - :raises SPSDKError: Raised when section is not instance of BootSectionV2 class - :raises SPSDKError: Raised when boot section has duplicate UID - """ - if not isinstance(section, BootSectionV2): - raise SPSDKError("Section is not instance of BootSectionV2 class") - duplicate_uid = find_first( - self._boot_sections, lambda bs: bs.uid == section.uid - ) - if duplicate_uid is not None: - raise SPSDKError(f"Boot section with duplicate UID: {str(section.uid)}") - self._boot_sections.append(section) - - def export(self, padding: Optional[bytes] = None) -> bytes: - """Serialize image object. - - :param padding: header padding (8 bytes) for testing purpose; None to use random values (recommended) - :return: exported bytes - :raises SPSDKError: Raised when there are no boot sections or is not signed or private keys are missing - :raises SPSDKError: Raised when there is invalid dek or mac - :raises SPSDKError: Raised when certificate data is not present - :raises SPSDKError: Raised when there is invalid certificate block - :raises SPSDKError: Raised when there is invalid length of exported data - """ - if len(self.dek) != 32 or len(self.mac) != 32: - raise SPSDKError("Invalid dek or mac") - # validate params - if not self._boot_sections: - raise SPSDKError("No boot section") - if self.signed and (self._cert_section is None): - raise SPSDKError("Certificate section is required for signed images") - # update internals - self.update() - # Add Image Header data - data = self._header.export(padding=padding) - # Add Image Header HMAC data - data += hmac(self.mac, data) - # Add DEK and MAC keys - data += aes_key_wrap(self.kek, self.dek + self.mac) - # Add Padding - data += padding if padding else random_bytes(8) - # Add Certificates data - if not self._header.nonce: - raise SPSDKError("There is no nonce in the header") - counter = Counter(self._header.nonce) - counter.increment(SecBootBlckSize.to_num_blocks(len(data))) - if self._cert_section is not None: - cert_sect_bin = self._cert_section.export( - dek=self.dek, mac=self.mac, counter=counter - ) - counter.increment(SecBootBlckSize.to_num_blocks(len(cert_sect_bin))) - data += cert_sect_bin - # Add Boot Sections data - for sect in self._boot_sections: - data += sect.export(dek=self.dek, mac=self.mac, counter=counter) - # Add Signature data - if self.signed: - if self.signature_provider is None: - raise SPSDKError( - "Signature provider is not assigned, cannot sign the image." - ) - if self.cert_block is None: - raise SPSDKError("Certificate block is not assigned.") - - public_key = self.cert_block.certificates[-1].get_public_key() - try_to_verify_public_key(self.signature_provider, public_key.export()) - data += self.signature_provider.get_signature(data) - - if len(data) != self.raw_size: - raise SPSDKError("Invalid length of exported data") - return data - - # pylint: disable=too-many-locals - @classmethod - def parse(cls, data: bytes, kek: bytes = bytes()) -> Self: - """Parse image from bytes. - - :param data: Raw data of parsed image - :param kek: The Key for unwrapping DEK and MAC keys (required) - :return: parsed image object - :raises SPSDKError: raised when header is in wrong format - :raises SPSDKError: raised when there is invalid header version - :raises SPSDKError: raised when signature is incorrect - :raises SPSDKError: Raised when kek is empty - :raises SPSDKError: raised when header's nonce is not present - """ - if not kek: - raise SPSDKError("kek cannot be empty") - index = 0 - header_raw_data = data[index : index + ImageHeaderV2.SIZE] - index += ImageHeaderV2.SIZE - header_mac_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 = aes_key_unwrap(kek, key_blob[:-8]) - dek = key_blob_unwrap[:32] - mac = key_blob_unwrap[32:] - header_mac_data_calc = hmac(mac, header_raw_data) - if header_mac_data != header_mac_data_calc: - raise SPSDKError("Invalid header MAC data") - # Parse Header - header = ImageHeaderV2.parse(header_raw_data) - if header.version != "2.0": - raise SPSDKError(f"Invalid Header Version: {header.version} instead 2.0") - image_size = header.image_blocks * 16 - # Initialize counter - if not header.nonce: - raise SPSDKError("Header's nonce not present") - counter = Counter(header.nonce) - counter.increment(SecBootBlckSize.to_num_blocks(index)) - # ... - signed = header.flags == 0x08 - adv_params = SBV2xAdvancedParams( - dek=dek, mac=mac, nonce=header.nonce, timestamp=header.timestamp - ) - obj = cls( - signed, - kek=kek, - product_version=str(header.product_version), - component_version=str(header.component_version), - build_number=header.build_number, - advanced_params=adv_params, - ) - # Parse Certificate section - if header.flags == 0x08: - cert_sect = CertSectionV2.parse( - data, index, dek=dek, mac=mac, counter=counter - ) - obj._cert_section = cert_sect - index += cert_sect.raw_size - # Check Signature - if not cert_sect.cert_block.verify_data( - data[image_size:], data[:image_size] - ): - raise SPSDKError("Parsing Certification section failed") - # Parse Boot Sections - while index < (image_size): - boot_section = BootSectionV2.parse( - data, index, dek=dek, mac=mac, counter=counter - ) - obj.add_boot_section(boot_section) - index += boot_section.raw_size - return obj - - ######################################################################################################################## # Secure Boot Image Class (Version 2.1) ######################################################################################################################## diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/uboot/__init__.py b/pynitrokey/trussed/bootloader/lpc55_upload/uboot/__init__.py deleted file mode 100644 index 580978fc..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/uboot/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Uboot device.""" diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/uboot/uboot.py b/pynitrokey/trussed/bootloader/lpc55_upload/uboot/uboot.py deleted file mode 100644 index 9337d045..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/uboot/uboot.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Simple Uboot serial console implementation.""" - -import logging - -from crcmod.predefined import mkPredefinedCrcFun -from hexdump import restore -from serial import Serial - -from ..exceptions import SPSDKError -from ..utils.misc import align, change_endianness, split_data - -logger = logging.getLogger(__name__) - - -class Uboot: - """Class for encapsulation of Uboot CLI interface.""" - - LINE_FEED = "\n" - ENCODING = "ascii" - READ_ALIGNMENT = 16 - DATA_BYTES_SPLIT = 4 - PROMPT = b"u-boot=> " - - def __init__( - self, port: str, timeout: int = 1, baudrate: int = 115200, crc: bool = True - ) -> None: - """Uboot constructor. - - :param port: TTY port - :param timeout: timeout in seconds, defaults to 1 - :param baudrate: baudrate, defaults to 115200 - :param crc: True if crc will be calculated, defaults to True - """ - self.port = port - self.baudrate = baudrate - self.timeout = timeout - self.is_opened = False - self.open() - self.crc = crc - - def calc_crc(self, data: bytes, address: int, count: int) -> None: - """Calculate CRC from the data. - - :param data: data to calculate CRC from - :param address: address from where the data should be calculated - :param count: count of bytes - :raises SPSDKError: Invalid CRC of data - """ - if not self.crc: - return - crc_command = f"crc32 {hex(address)} {hex(count)}" - self.write(crc_command) - hexdump_str = self.LINE_FEED.join(self.read_output().splitlines()[1:-1]) - crc_obtained = "0x" + hexdump_str[-8:] - logger.debug(f"CRC command:\n{crc_command}\n{crc_obtained}") - crc_function = mkPredefinedCrcFun("crc-32") - calculated_crc = hex(crc_function(data)) - logger.debug(f"Calculated CRC {calculated_crc}") - if calculated_crc != crc_obtained: - raise SPSDKError(f"Invalid CRC of data {calculated_crc} != {crc_obtained}") - - def open(self) -> None: - """Open uboot device.""" - self._device = Serial( - port=self.port, timeout=self.timeout, baudrate=self.baudrate - ) - self.is_opened = True - - def close(self) -> None: - """Close uboot device.""" - self._device.close() - self.is_opened = False - - def read(self, length: int) -> str: - """Read specified number of charactrs from uboot CLI. - - :param length: count of read characters - :return: encoded string - """ - output = self._device.read(length) - return output.decode(encoding=self.ENCODING) - - def read_output(self) -> str: - """Read CLI output until prompt. - - :return: ASCII encoded output - """ - return self._device.read_until(expected=self.PROMPT).decode(self.ENCODING) - - def write(self, data: str) -> None: - """Write ASCII decoded data to CLI. Append LINE FEED if not present. - - :param data: ASCII decoded data - """ - if self.LINE_FEED not in data: - data += self.LINE_FEED - data_bytes = bytes(data, encoding=self.ENCODING) - self._device.write(data_bytes) - - def read_memory(self, address: int, count: int) -> bytes: - """Read memory using the md command. Optionally calculate CRC. - - :param address: Address in memory - :param count: Count of bytes - :return: data as bytes - """ - count = align(count, self.READ_ALIGNMENT) - md_command = f"md.b {hex(address)} {hex(count)}" - self.write(md_command) - hexdump_str = self.LINE_FEED.join(self.read_output().splitlines()[1:-1]) - logger.debug(f"read_memory:\n{md_command}\n{hexdump_str}") - data = restore(hexdump_str) - self.calc_crc(data, address, count) - - return data - - def write_memory(self, address: int, data: bytes) -> None: - """Write memory and optionally calculate CRC. - - :param address: Address in memory - :param data: data as bytes - """ - start_address = address - for splitted_data in split_data(data, self.DATA_BYTES_SPLIT): - mw_command = f"mw.l {hex(address)} {change_endianness(splitted_data).hex()}" - logger.debug(f"write_memory: {mw_command}") - self.write(mw_command) - address += len(splitted_data) - self.read_output() - - self.calc_crc(data, start_address, len(data)) diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/iee.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/iee.py deleted file mode 100644 index e2e9789e..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/iee.py +++ /dev/null @@ -1,838 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2022-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""The module provides support for IEE for RTxxxx devices.""" - -import logging -from copy import deepcopy -from struct import pack -from typing import Any, Dict, List, Optional, Union - -from crcmod.predefined import mkPredefinedCrcFun - -from ... import version as spsdk_version -from ...apps.utils.utils import filepath_from_config -from ...crypto.rng import random_bytes -from ...crypto.symmetric import Counter, aes_ctr_encrypt, aes_xts_encrypt -from ...exceptions import SPSDKError, SPSDKValueError -from ...utils.database import DatabaseManager, get_db, get_families, get_schema_file -from ...utils.images import BinaryImage -from ...utils.misc import ( - Endianness, - align_block, - load_hex_string, - reverse_bytes_in_longs, - split_data, - value_to_bytes, - value_to_int, -) -from ...utils.registers import Registers -from ...utils.schema_validator import CommentedConfig -from ...utils.spsdk_enum import SpsdkEnum - -logger = logging.getLogger(__name__) - - -class IeeKeyBlobLockAttributes(SpsdkEnum): - """IEE keyblob lock attributes.""" - - LOCK = (0x95, "LOCK") # IEE region lock. - UNLOCK = (0x59, "UNLOCK") # IEE region unlock. - - -class IeeKeyBlobKeyAttributes(SpsdkEnum): - """IEE keyblob key attributes.""" - - CTR128XTS256 = (0x5A, "CTR128XTS256") # AES 128 bits (CTR), 256 bits (XTS) - CTR256XTS512 = (0xA5, "CTR256XTS512") # AES 256 bits (CTR), 512 bits (XTS) - - -class IeeKeyBlobModeAttributes(SpsdkEnum): - """IEE Keyblob mode attributes.""" - - Bypass = (0x6A, "Bypass") # AES encryption/decryption bypass - AesXTS = (0xA6, "AesXTS") # AES XTS mode - AesCTRWAddress = (0x66, "AesCTRWAddress") # AES CTR w address binding mode - AesCTRWOAddress = (0xAA, "AesCTRWOAddress") # AES CTR w/o address binding mode - AesCTRkeystream = (0x19, "AesCTRkeystream") # AES CTR keystream only - - -class IeeKeyBlobWritePmsnAttributes(SpsdkEnum): - """IEE keblob write permission attributes.""" - - ENABLE = (0x99, "ENABLE") # Enable write permission in APC IEE - DISABLE = (0x11, "DISABLE") # Disable write permission in APC IEE - - -class IeeKeyBlobAttribute: - """IEE Keyblob Attribute. - - | typedef struct _iee_keyblob_attribute - | { - | uint8_t lock; # IEE Region Lock control flag. - | uint8_t keySize; # IEE AES key size. - | uint8_t aesMode; # IEE AES mode. - | uint8_t reserved; # Reserved. - | } iee_keyblob_attribute_t; - """ - - _FORMAT = " None: - """IEE keyblob constructor. - - :param lock: IeeKeyBlobLockAttributes - :param key_attribute: IeeKeyBlobKeyAttributes - :param aes_mode: IeeKeyBlobModeAttributes - """ - self.lock = lock - self.key_attribute = key_attribute - self.aes_mode = aes_mode - - @property - def ctr_mode(self) -> bool: - """Return true if AES mode is CTR. - - :return: True if AES-CTR, false otherwise - """ - if self.aes_mode in [ - IeeKeyBlobModeAttributes.AesCTRWAddress, - IeeKeyBlobModeAttributes.AesCTRWOAddress, - IeeKeyBlobModeAttributes.AesCTRkeystream, - ]: - return True - return False - - @property - def key1_size(self) -> int: - """Return IEE key size based on selected mode. - - :return: Key size in bytes - """ - if self.key_attribute == IeeKeyBlobKeyAttributes.CTR128XTS256: - return 16 - return 32 - - @property - def key2_size(self) -> int: - """Return IEE key size based on selected mode. - - :return: Key size in bytes - """ - if self.key_attribute == IeeKeyBlobKeyAttributes.CTR128XTS256: - return 16 - if self.ctr_mode: - return 16 - return 32 - - def export(self) -> bytes: - """Export binary representation of KeyBlobAttribute. - - :return: serialized binary data - """ - return pack( - self._FORMAT, self.lock.tag, self.key_attribute.tag, self.aes_mode.tag, 0 - ) - - -class IeeKeyBlob: - """IEE KeyBlob. - - | typedef struct _iee_keyblob_ - | { - | uint32_t header; # IEE Key Blob header tag. - | uint32_t version; # IEE Key Blob version, upward compatible. - | iee_keyblob_attribute_t attribute; # IEE configuration attribute. - | uint32_t pageOffset; # IEE page offset. - | uint32_t key1[IEE_MAX_AES_KEY_SIZE_IN_BYTE / - | sizeof(uint32_t)]; # Encryption key1 for XTS-AES mode, encryption key for AES-CTR mode. - | uint32_t key2[IEE_MAX_AES_KEY_SIZE_IN_BYTE / - | sizeof(uint32_t)]; # Encryption key2 for XTS-AES mode, initial counter for AES-CTR mode. - | uint32_t startAddr; # Physical address of encryption region. - | uint32_t endAddr; # Physical address of encryption region. - | uint32_t reserved; # Reserved word. - | uint32_t crc32; # Entire IEE Key Blob CRC32 value. Must be the last struct member. - | } iee_keyblob_t - """ - - _FORMAT = "LL4BL8L8LLLLL96B" - - HEADER_TAG = 0x49454542 - # Tag used in keyblob header - # (('I' << 24) | ('E' << 16) | ('E' << 8) | ('B' << 0)) - KEYBLOB_VERSION = 0x56010000 - # Identifier of IEE keyblob version - # (('V' << 24) | (1 << 16) | (0 << 8) | (0 << 0)) - KEYBLOB_OFFSET = 0x1000 - - _IEE_ENCR_BLOCK_SIZE_XTS = 0x1000 - - _ENCRYPTION_BLOCK_SIZE = 0x10 - - _START_ADDR_MASK = 0x400 - 1 - # Region addresses are modulo 1024 - - _END_ADDR_MASK = 0x3F8 - - def __init__( - self, - attributes: IeeKeyBlobAttribute, - start_addr: int, - end_addr: int, - key1: Optional[bytes] = None, - key2: Optional[bytes] = None, - page_offset: int = 0, - crc: Optional[bytes] = None, - ): - """Constructor. - - :param attributes: IEE keyblob attributes - :param start_addr: start address of the region - :param end_addr: end address of the region - :param key1: Encryption key1 for XTS-AES mode, encryption key for AES-CTR mode. - :param key2: Encryption key2 for XTS-AES mode, initial_counter for AES-CTR mode. - :param crc: optional value for unused CRC fill (for testing only); None to use calculated value - :raises SPSDKError: Start or end address are not aligned - :raises SPSDKError: When there is invalid key - :raises SPSDKError: When there is invalid start/end address - """ - self.attributes = attributes - - if key1 is None: - key1 = random_bytes(self.attributes.key1_size) - if key2 is None: - key2 = random_bytes(self.attributes.key2_size) - - key1 = value_to_bytes(key1, byte_cnt=self.attributes.key1_size) - key2 = value_to_bytes(key2, byte_cnt=self.attributes.key2_size) - - if start_addr < 0 or start_addr > end_addr or end_addr > 0xFFFFFFFF: - raise SPSDKError("Invalid start/end address") - - if (start_addr & self._START_ADDR_MASK) != 0: - raise SPSDKError( - f"Start address must be aligned to {hex(self._START_ADDR_MASK + 1)} boundary" - ) - - self.start_addr = start_addr - self.end_addr = end_addr - - self.key1 = key1 - self.key2 = key2 - self.page_offset = page_offset - - self.crc_fill = crc - - def __str__(self) -> str: - """Text info about the instance.""" - msg = "" - msg += f"KEY 1: {self.key1.hex()}\n" - msg += f"KEY 2: {self.key2.hex()}\n" - msg += f"Start Addr: {hex(self.start_addr)}\n" - msg += f"End Addr: {hex(self.end_addr)}\n" - return msg - - def plain_data(self) -> bytes: - """Plain data for selected key range. - - :return: key blob exported into binary form (serialization) - """ - result = bytes() - result += pack(" bool: - """Whether key blob contains specified address. - - :param addr: to be tested - :return: True if yes, False otherwise - """ - return self.start_addr <= addr <= self.end_addr - - def matches_range(self, image_start: int, image_end: int) -> bool: - """Whether key blob matches address range of the image to be encrypted. - - :param image_start: start address of the image - :param image_end: last address of the image - :return: True if yes, False otherwise - """ - return self.contains_addr(image_start) and self.contains_addr(image_end) - - def encrypt_image_xts(self, base_address: int, data: bytes) -> bytes: - """Encrypt specified data using AES-XTS. - - :param base_address: of the data in target memory; must be >= self.start_addr - :param data: to be encrypted (e.g. plain image); base_address + len(data) must be <= self.end_addr - :return: encrypted data - """ - encrypted_data = bytes() - current_start = base_address - key1 = reverse_bytes_in_longs(self.key1) - key2 = reverse_bytes_in_longs(self.key2) - - for block in split_data(bytearray(data), self._IEE_ENCR_BLOCK_SIZE_XTS): - tweak = self.calculate_tweak(current_start) - - encrypted_block = aes_xts_encrypt( - key1 + key2, - block, - tweak, - ) - encrypted_data += encrypted_block - current_start += len(block) - - return encrypted_data - - def encrypt_image_ctr(self, base_address: int, data: bytes) -> bytes: - """Encrypt specified data using AES-CTR. - - :param base_address: of the data in target memory; must be >= self.start_addr - :param data: to be encrypted (e.g. plain image); base_address + len(data) must be <= self.end_addr - :return: encrypted data - """ - encrypted_data = bytes() - key = reverse_bytes_in_longs(self.key1) - nonce = reverse_bytes_in_longs(self.key2) - - counter = Counter( - nonce, ctr_value=base_address >> 4, ctr_byteorder_encoding=Endianness.BIG - ) - - for block in split_data(bytearray(data), self._ENCRYPTION_BLOCK_SIZE): - encrypted_block = aes_ctr_encrypt( - key, - block, - counter.value, - ) - encrypted_data += encrypted_block - counter.increment(self._ENCRYPTION_BLOCK_SIZE >> 4) - - return encrypted_data - - def encrypt_image(self, base_address: int, data: bytes) -> bytes: - """Encrypt specified data. - - :param base_address: of the data in target memory; must be >= self.start_addr - :param data: to be encrypted (e.g. plain image); base_address + len(data) must be <= self.end_addr - :return: encrypted data - :raises SPSDKError: If start address is not valid - :raises NotImplementedError: AES-CTR is not implemented yet - """ - if base_address % 16 != 0: - raise SPSDKError( - "Invalid start address" - ) # Start address has to be 16 byte aligned - data = align_block(data, self._ENCRYPTION_BLOCK_SIZE) # align data length - data_len = len(data) - - # check start and end addresses - if not self.matches_range(base_address, base_address + data_len - 1): - logger.warning( - f"Image address range is not within key blob: {hex(self.start_addr)}-{hex(self.end_addr)}." - ) - - if self.attributes.ctr_mode: - return self.encrypt_image_ctr(base_address, data) - return self.encrypt_image_xts(base_address, data) - - @staticmethod - def calculate_tweak(address: int) -> bytes: - """Calculate tweak value for AES-XTS encryption based on the address value. - - :param address: start address of encryption - :return: 16 byte tweak values - """ - sector = address >> 12 - tweak = bytearray(16) - for n in range(16): - tweak[n] = sector & 0xFF - sector = sector >> 8 - return bytes(tweak) - - -class Iee: - """IEE: Inline Encryption Engine.""" - - IEE_DATA_UNIT = 0x1000 - IEE_KEY_BLOBS_SIZE = 384 - - def __init__(self) -> None: - """Constructor.""" - self._key_blobs: List[IeeKeyBlob] = [] - - def __getitem__(self, index: int) -> IeeKeyBlob: - return self._key_blobs[index] - - def __setitem__(self, index: int, value: IeeKeyBlob) -> None: - self._key_blobs.remove(self._key_blobs[index]) - self._key_blobs.insert(index, value) - - def add_key_blob(self, key_blob: IeeKeyBlob) -> None: - """Add key for specified address range. - - :param key_blob: to be added - """ - self._key_blobs.append(key_blob) - - def encrypt_image(self, image: bytes, base_addr: int) -> bytes: - """Encrypt image with all available keyblobs. - - :param image: plain image to be encrypted - :param base_addr: where the image will be located in target processor - :return: encrypted image - """ - encrypted_data = bytearray(image) - addr = base_addr - for block in split_data(image, self.IEE_DATA_UNIT): - for key_blob in self._key_blobs: - if key_blob.matches_range(addr, addr + len(block)): - logger.debug( - f"Encrypting {hex(addr)}:{hex(len(block) + addr)}" - f" with keyblob: \n {str(key_blob)}" - ) - encrypted_data[ - addr - base_addr : len(block) + addr - base_addr - ] = key_blob.encrypt_image(addr, block) - addr += len(block) - - return bytes(encrypted_data) - - def get_key_blobs(self) -> bytes: - """Get key blobs. - - :return: Binary key blobs joined together - """ - result = bytes() - for key_blob in self._key_blobs: - result += key_blob.plain_data() - - # return result - return align_block(result, self.IEE_KEY_BLOBS_SIZE) - - def encrypt_key_blobs( - self, - ibkek1: Union[bytes, str], - ibkek2: Union[bytes, str], - keyblob_address: int, - ) -> bytes: - """Encrypt keyblobs and export them as binary. - - :param ibkek1: key encryption key AES-XTS 256 bit - :param ibkek2: key encryption key AES-XTS 256 bit - :param keyblob_address: keyblob base address - :return: encrypted keyblobs - """ - plain_key_blobs = self.get_key_blobs() - - ibkek1 = reverse_bytes_in_longs(value_to_bytes(ibkek1, byte_cnt=32)) - logger.debug(f"IBKEK1: {' '.join(f'{b:02x}' for b in ibkek1)}") - ibkek2 = reverse_bytes_in_longs(value_to_bytes(ibkek2, byte_cnt=32)) - logger.debug(f"IBKEK2 {' '.join(f'{b:02x}' for b in ibkek2)}") - - tweak = IeeKeyBlob.calculate_tweak(keyblob_address) - return aes_xts_encrypt( - ibkek1 + ibkek2, - plain_key_blobs, - tweak, - ) - - -class IeeNxp(Iee): - """IEE: Inline Encryption Engine.""" - - def __init__( - self, - family: str, - keyblob_address: int, - ibkek1: Union[bytes, str], - ibkek2: Union[bytes, str], - key_blobs: Optional[List[IeeKeyBlob]] = None, - binaries: Optional[BinaryImage] = None, - ) -> None: - """Constructor. - - :param family: Device family - :param ibkek1: 256 bit key to encrypt IEE keyblob - :param ibkek2: 256 bit key to encrypt IEE keyblob - :param key_blobs: Optional Key blobs to add to IEE, defaults to None - :raises SPSDKValueError: Unsupported family - """ - super().__init__() - - if family not in self.get_supported_families(): - raise SPSDKValueError(f"Unsupported family{family} by IEE") - - self.family = family - self.ibkek1 = bytes.fromhex(ibkek1) if isinstance(ibkek1, str) else ibkek1 - self.ibkek2 = bytes.fromhex(ibkek2) if isinstance(ibkek2, str) else ibkek2 - self.keyblob_address = keyblob_address - self.binaries = binaries - - self.db = get_db(family, "latest") - self.blobs_min_cnt = self.db.get_int(DatabaseManager.IEE, "key_blob_min_cnt") - self.blobs_max_cnt = self.db.get_int(DatabaseManager.IEE, "key_blob_max_cnt") - self.generate_keyblob = self.db.get_bool( - DatabaseManager.IEE, "generate_keyblob" - ) - - if key_blobs: - for key_blob in key_blobs: - self.add_key_blob(key_blob) - - def export_key_blobs(self) -> bytes: - """Export encrypted keyblobs in binary. - - :return: Encrypted keyblobs - """ - return self.encrypt_key_blobs(self.ibkek1, self.ibkek2, self.keyblob_address) - - def export_image(self) -> Optional[BinaryImage]: - """Export encrypted image. - - :return: Encrypted image - """ - if self.binaries is None: - return None - self.binaries.validate() - - binaries: BinaryImage = deepcopy(self.binaries) - - for binary in binaries.sub_images: - if binary.binary: - binary.binary = self.encrypt_image( - binary.binary, binary.absolute_address + self.keyblob_address - ) - for segment in binary.sub_images: - if segment.binary: - segment.binary = self.encrypt_image( - segment.binary, - segment.absolute_address + self.keyblob_address, - ) - - binaries.validate() - return binaries - - def get_blhost_script_otp_kek(self) -> str: - """Create BLHOST script to load fuses needed to run IEE with OTP fuses. - - :return: BLHOST script that loads the keys into fuses. - """ - if not self.db.get_bool(DatabaseManager.IEE, "has_kek_fuses", default=False): - logger.debug(f"The {self.family} has no IEE KEK fuses") - return "" - - xml_fuses = self.db.get_file_path( - DatabaseManager.IEE, "reg_fuses", default=None - ) - if not xml_fuses: - logger.debug(f"The {self.family} has no IEE fuses definition") - return "" - - fuses = Registers(self.family, base_endianness=Endianness.LITTLE) - grouped_regs = self.db.get_list( - DatabaseManager.IEE, "grouped_registers", default=None - ) - - fuses.load_registers_from_xml(xml_fuses, grouped_regs=grouped_regs) - fuses.find_reg("USER_KEY1").set_value(self.ibkek1) - fuses.find_reg("USER_KEY2").set_value(self.ibkek2) - - load_iee = fuses.find_reg("LOAD_IEE_KEY") - load_iee.find_bitfield("LOAD_IEE_KEY_BITFIELD").set_value(1) - - encrypt_engine = fuses.find_reg("ENCRYPT_XIP_ENGINE") - encrypt_engine.find_bitfield("ENCRYPT_XIP_ENGINE_BITFIELD").set_value(1) - - boot_cfg = fuses.find_reg("BOOT_CFG") - boot_cfg.find_bitfield("ENCRYPT_XIP_EN_BITFIELD").set_value(1) - - ibkek_lock = fuses.find_reg("USER_KEY_RLOCK") - ibkek_lock.find_bitfield("USER_KEY1_RLOCK").set_value(1) - ibkek_lock.find_bitfield("USER_KEY2_RLOCK").set_value(1) - - ret = ( - "# BLHOST IEE fuses programming script\n" - f"# Generated by SPSDK {spsdk_version}\n" - f"# Chip: {self.family} \n\n" - ) - - ret += f"# OTP IBKEK1: {self.ibkek1.hex()}\n\n" - for reg in fuses.find_reg("USER_KEY1").sub_regs: - ret += f"# {reg.name} fuse.\n" - ret += f"efuse-program-once {hex(reg.offset)} 0x{reg.get_hex_value(raw=True)} --no-verify\n" - - ret += f"\n\n# OTP IBKEK2: {self.ibkek2.hex()}\n\n" - for reg in fuses.find_reg("USER_KEY2").sub_regs: - ret += f"# {reg.name} fuse.\n" - ret += f"efuse-program-once {hex(reg.offset)} 0x{reg.get_hex_value(raw=True)} --no-verify\n" - - ret += f"\n\n# {load_iee.name} fuse.\n" - for bitfield in load_iee.get_bitfields(): - ret += f"# {bitfield.name}: {bitfield.get_enum_value()}\n" - ret += f"efuse-program-once {hex(load_iee.offset)} 0x{load_iee.get_hex_value(raw=True)} --no-verify\n" - - ret += f"\n\n# {encrypt_engine.name} fuse.\n" - for bitfield in encrypt_engine.get_bitfields(): - ret += f"# {bitfield.name}: {bitfield.get_enum_value()}\n" - ret += ( - f"efuse-program-once {hex(encrypt_engine.offset)} " - f"0x{encrypt_engine.get_hex_value(raw=True)} --no-verify\n" - ) - - ret += f"\n\n# {ibkek_lock.name} fuse.\n" - for bitfield in ibkek_lock.get_bitfields(): - ret += f"# {bitfield.name}: {bitfield.get_enum_value()}\n" - ret += f"efuse-program-once {hex(ibkek_lock.offset)} 0x{ibkek_lock.get_hex_value(raw=True)} --no-verify\n" - - ret += f"\n\n# {boot_cfg.name} fuse.\n" - ret += ( - "WARNING!! Check SRM and set all desired bitfields for boot configuration" - ) - for bitfield in boot_cfg.get_bitfields(): - ret += f"# {bitfield.name}: {bitfield.get_enum_value()}\n" - ret += ( - f"# efuse-program-once {hex(boot_cfg.offset)} " - f"0x{boot_cfg.get_hex_value(raw=True)} --no-verify\n" - ) - - return ret - - def binary_image( - self, - plain_data: bool = False, - data_alignment: int = 16, - keyblob_name: str = "iee_keyblob.bin", - image_name: str = "encrypted.bin", - ) -> BinaryImage: - """Get the IEE Binary Image representation. - - :param plain_data: Binary representation in plain format, defaults to False - :param data_alignment: Alignment of data part key blobs. - :param keyblob_name: Filename of the IEE keyblob - :param image_name: Filename of the IEE image - :return: IEE in BinaryImage. - """ - iee = BinaryImage(image_name, offset=self.keyblob_address) - if self.generate_keyblob: - # Add mandatory IEE keyblob - iee_keyblobs = ( - self.get_key_blobs() if plain_data else self.export_key_blobs() - ) - iee.add_image( - BinaryImage( - keyblob_name, - offset=0, - description=f"IEE keyblobs {self.family}", - binary=iee_keyblobs, - ) - ) - binaries = self.export_image() - - if binaries: - binaries.alignment = data_alignment - binaries.validate() - iee.add_image(binaries) - - return iee - - @staticmethod - def get_supported_families() -> List[str]: - """Get all supported families for AHAB container. - - :return: List of supported families. - """ - return get_families(DatabaseManager.IEE) - - @staticmethod - def get_validation_schemas(family: str) -> List[Dict[str, Any]]: - """Get list of validation schemas. - - :param family: Family for which the template should be generated. - :return: Validation list of schemas. - """ - if family not in IeeNxp.get_supported_families(): - return [] - - database = get_db(family, "latest") - schemas = get_schema_file(DatabaseManager.IEE) - family_sch = schemas["iee_family"] - family_sch["properties"]["family"]["enum"] = IeeNxp.get_supported_families() - family_sch["properties"]["family"]["template_value"] = family - ret = [family_sch, schemas["iee_output"], schemas["iee"]] - additional_schemes = database.get_list( - DatabaseManager.IEE, "additional_template", default=[] - ) - ret.extend([schemas[x] for x in additional_schemes]) - return ret - - @staticmethod - def get_validation_schemas_family() -> List[Dict[str, Any]]: - """Get list of validation schemas for family key. - - :return: Validation list of schemas. - """ - schemas = get_schema_file(DatabaseManager.IEE) - family_sch = schemas["iee_family"] - family_sch["properties"]["family"]["enum"] = IeeNxp.get_supported_families() - return [family_sch] - - @staticmethod - def generate_config_template(family: str) -> Dict[str, Any]: - """Generate IEE configuration template. - - :param family: Family for which the template should be generated. - :return: Dictionary of individual templates (key is name of template, value is template itself). - """ - val_schemas = IeeNxp.get_validation_schemas(family) - database = get_db(family, "latest") - - if val_schemas: - template_note = database.get_str( - DatabaseManager.IEE, "additional_template_text", default="" - ) - title = ( - f"IEE: Inline Encryption Engine Configuration template for {family}." - ) - - yaml_data = CommentedConfig( - title, val_schemas, note=template_note - ).get_template() - - return {f"{family}_iee": yaml_data} - - return {} - - @staticmethod - def load_from_config( - config: Dict[str, Any], - config_dir: str, - search_paths: Optional[List[str]] = None, - ) -> "IeeNxp": - """Converts the configuration option into an IEE image object. - - "config" content array of containers configurations. - - :param config: array of IEE configuration dictionaries. - :param config_dir: directory where the config is located - :param search_paths: List of paths where to search for the file, defaults to None - :return: initialized IEE object. - """ - iee_config: List[Dict[str, Any]] = config.get( - "key_blobs", [config.get("key_blob")] - ) - family = config["family"] - ibkek1 = load_hex_string( - config.get( - "ibkek1", - "0x000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", - ), - 32, - ) - ibkek2 = load_hex_string( - config.get( - "ibkek2", - "0x202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F", - ), - 32, - ) - - logger.debug(f"Loaded IBKEK1: {ibkek1.hex()}") - logger.debug(f"Loaded IBKEK2: {ibkek2.hex()}") - - keyblob_address = value_to_int(config["keyblob_address"]) - start_address = min( - [value_to_int(addr.get("start_address", 0xFFFFFFFF)) for addr in iee_config] - ) - - data_blobs: Optional[List[Dict]] = config.get("data_blobs") - binaries = None - if data_blobs: - # start address to calculate offset from keyblob, min from keyblob or data blob address - # pylint: disable-next=nested-min-max - start_address = min( - min( - [ - value_to_int(addr.get("address", 0xFFFFFFFF)) - for addr in data_blobs - ] - ), - start_address, - ) - binaries = BinaryImage( - filepath_from_config( - config, - "encrypted_name", - "encrypted_blobs", - config_dir, - config["output_folder"], - ), - offset=start_address - keyblob_address, - alignment=IeeKeyBlob._ENCRYPTION_BLOCK_SIZE, - ) - for data_blob in data_blobs: - address = value_to_int( - data_blob.get("address", 0), keyblob_address + binaries.offset - ) - - binary = BinaryImage.load_binary_image( - path=data_blob["data"], - search_paths=search_paths, - offset=address - keyblob_address - binaries.offset, - alignment=IeeKeyBlob._ENCRYPTION_BLOCK_SIZE, - size=0, - ) - - binaries.add_image(binary) - - iee = IeeNxp(family, keyblob_address, ibkek1, ibkek2, binaries=binaries) - - for key_blob_cfg in iee_config: - aes_mode = key_blob_cfg["aes_mode"] - region_lock = "LOCK" if key_blob_cfg.get("region_lock") else "UNLOCK" - key_size = key_blob_cfg["key_size"] - - attributes = IeeKeyBlobAttribute( - IeeKeyBlobLockAttributes.from_label(region_lock), - IeeKeyBlobKeyAttributes.from_label(key_size), - IeeKeyBlobModeAttributes.from_label(aes_mode), - ) - - key1 = load_hex_string(key_blob_cfg["key1"], attributes.key1_size) - key2 = load_hex_string(key_blob_cfg["key2"], attributes.key2_size) - - start_addr = value_to_int(key_blob_cfg.get("start_address", start_address)) - end_addr = value_to_int(key_blob_cfg.get("end_address", 0xFFFFFFFF)) - page_offset = value_to_int(key_blob_cfg.get("page_offset", 0)) - - iee.add_key_blob( - IeeKeyBlob( - attributes=attributes, - start_addr=start_addr, - end_addr=end_addr, - key1=key1, - key2=key2, - page_offset=page_offset, - ) - ) - - return iee diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/otfad.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/otfad.py index cb49f703..dc7ae1d7 100644 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/otfad.py +++ b/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/otfad.py @@ -7,37 +7,8 @@ """The module provides support for On-The-Fly encoding for RTxxx devices.""" -import logging -import os -from copy import deepcopy -from struct import pack from typing import Any, Dict, List, Optional, Union -from crcmod.predefined import mkPredefinedCrcFun - -from ... import version as spsdk_version -from ...apps.utils.utils import filepath_from_config -from ...crypto.rng import random_bytes -from ...crypto.symmetric import Counter, aes_ctr_encrypt, aes_key_wrap -from ...exceptions import SPSDKError, SPSDKValueError -from ...utils.database import DatabaseManager, get_db, get_families, get_schema_file -from ...utils.exceptions import SPSDKRegsErrorBitfieldNotFound -from ...utils.images import BinaryImage -from ...utils.misc import ( - Endianness, - align_block, - load_binary, - load_hex_string, - reverse_bits_in_bytes, - split_data, - value_to_bytes, - value_to_int, -) -from ...utils.registers import Registers -from ...utils.schema_validator import CommentedConfig - -logger = logging.getLogger(__name__) - class KeyBlob: """OTFAD KeyBlob: The class specifies AES key and counter initial value for specified address range. @@ -355,632 +326,3 @@ def is_encrypted(self) -> bool: (self.key_flags & (self.KEY_FLAG_ADE | self.KEY_FLAG_VLD)) == (self.KEY_FLAG_ADE | self.KEY_FLAG_VLD) ) - - -class Otfad: - """OTFAD: On-the-Fly AES Decryption Module.""" - - OTFAD_DATA_UNIT = 0x400 - - def __init__(self) -> None: - """Constructor.""" - self._key_blobs: List[KeyBlob] = [] - - def __getitem__(self, index: int) -> KeyBlob: - return self._key_blobs[index] - - def __setitem__(self, index: int, value: KeyBlob) -> None: - self._key_blobs.remove(self._key_blobs[index]) - self._key_blobs.insert(index, value) - - def __len__(self) -> int: - """Count of keyblobs.""" - return len(self._key_blobs) - - def add_key_blob(self, key_blob: KeyBlob) -> None: - """Add key for specified address range. - - :param key_blob: to be added - """ - self._key_blobs.append(key_blob) - - def encrypt_image(self, image: bytes, base_addr: int, byte_swap: bool) -> bytes: - """Encrypt image with all available keyblobs. - - :param image: plain image to be encrypted - :param base_addr: where the image will be located in target processor - :param byte_swap: this probably depends on the flash device, how bytes are organized there - :return: encrypted image - """ - encrypted_data = bytearray(image) - addr = base_addr - for block in split_data(image, self.OTFAD_DATA_UNIT): - for key_blob in self._key_blobs: - if key_blob.matches_range(addr, addr + len(block)): - logger.debug( - f"Encrypting {hex(addr)}:{hex(len(block) + addr)}" - f" with keyblob: \n {str(key_blob)}" - ) - encrypted_data[ - addr - base_addr : len(block) + addr - base_addr - ] = key_blob.encrypt_image( - addr, block, byte_swap, counter_value=addr - ) - addr += len(block) - - return bytes(encrypted_data) - - def get_key_blobs(self) -> bytes: - """Get key blobs. - - :return: Binary key blobs joined together - """ - result = bytes() - for key_blob in self._key_blobs: - result += key_blob.plain_data() - return align_block( - result, 256 - ) # this is for compatibility with elftosb, probably need FLASH sector size - - def encrypt_key_blobs( - self, - kek: Union[bytes, str], - key_scramble_mask: Optional[int] = None, - key_scramble_align: Optional[int] = None, - byte_swap_cnt: int = 0, - ) -> bytes: - """Encrypt key blobs with specified key. - - :param kek: key to encode key blobs - :param key_scramble_mask: 32-bit scramble key, if KEK scrambling is desired. - :param key_scramble_align: 8-bit scramble align, if KEK scrambling is desired. - :param byte_swap_cnt: Encrypted keyblob reverse byte count, 0 means NO reversing is enabled - :raises SPSDKValueError: Invalid input value. - :return: encrypted binary key blobs joined together - """ - if isinstance(kek, str): - kek = bytes.fromhex(kek) - scramble_enabled = ( - key_scramble_mask is not None and key_scramble_align is not None - ) - if scramble_enabled: - assert key_scramble_mask and key_scramble_align - if key_scramble_mask >= 1 << 32: - raise SPSDKValueError("OTFAD Key scramble mask has invalid length") - if key_scramble_align >= 1 << 8: - raise SPSDKValueError("OTFAD Key scramble align has invalid length") - - logger.debug("The scrambling of keys is enabled.") - key_scramble_mask_inv = reverse_bits_in_bytes( - key_scramble_mask.to_bytes(4, byteorder=Endianness.BIG.value) - ) - logger.debug(f"The inverted scramble key is: {key_scramble_mask_inv.hex()}") - result = bytes() - scrambled = bytes() - for i, key_blob in enumerate(self._key_blobs): - if scramble_enabled: - assert key_scramble_mask and key_scramble_align - scrambled = bytearray(kek) - long_ix = (key_scramble_align >> (i * 2)) & 0x03 - for j in range(4): - scrambled[(long_ix * 4) + j] ^= key_scramble_mask_inv[j] - - logger.debug( - f"Used KEK for keyblob{i} encryption is: {scrambled.hex() if scramble_enabled else kek.hex()}" - ) - - result += key_blob.export( - scrambled if scramble_enabled else kek, byte_swap_cnt=byte_swap_cnt - ) - return align_block( - result, 256 - ) # this is for compatibility with elftosb, probably need FLASH sector size - - def __str__(self) -> str: - """Text info about the instance.""" - msg = "Key-Blob\n" - for index, key_blob in enumerate(self._key_blobs): - msg += f"Key-Blob {str(index)}:\n" - msg += str(key_blob) - return msg - - -class OtfadNxp(Otfad): - """OTFAD: On-the-Fly AES Decryption Module with reflecting of NXP parts.""" - - def __init__( - self, - family: str, - kek: Union[bytes, str], - table_address: int = 0, - key_blobs: Optional[List[KeyBlob]] = None, - key_scramble_mask: Optional[int] = None, - key_scramble_align: Optional[int] = None, - binaries: Optional[BinaryImage] = None, - ) -> None: - """Constructor. - - :param family: Device family - :param kek: KEK to encrypt OTFAD table - :param table_address: Absolute address of OTFAD table. - :param key_blobs: Optional Key blobs to add to OTFAD, defaults to None - :param key_scramble_mask: If defined, the key scrambling algorithm will be applied. - ('key_scramble_align' must be defined also) - :param key_scramble_align: If defined, the key scrambling algorithm will be applied. - ('key_scramble_mask' must be defined also) - :raises SPSDKValueError: Unsupported family - """ - super().__init__() - - if family not in self.get_supported_families(): - raise SPSDKValueError(f"Unsupported family{family} by OTFAD") - - if (key_scramble_align is None and key_scramble_mask) or ( - key_scramble_align and key_scramble_mask is None - ): - raise SPSDKValueError("Key Scrambling is not fully defined") - - self.family = family - self.kek = bytes.fromhex(kek) if isinstance(kek, str) else kek - self.key_scramble_mask = key_scramble_mask - self.key_scramble_align = key_scramble_align - self.table_address = table_address - self.db = get_db(family, "latest") - self.blobs_min_cnt = self.db.get_int(DatabaseManager.OTFAD, "key_blob_min_cnt") - self.blobs_max_cnt = self.db.get_int(DatabaseManager.OTFAD, "key_blob_max_cnt") - self.byte_swap = self.db.get_bool(DatabaseManager.OTFAD, "byte_swap") - self.key_blob_rec_size = self.db.get_int( - DatabaseManager.OTFAD, "key_blob_rec_size" - ) - self.keyblob_byte_swap_cnt = self.db.get_int( - DatabaseManager.OTFAD, "keyblob_byte_swap_cnt" - ) - assert self.keyblob_byte_swap_cnt in [0, 2, 4, 8, 16] - self.binaries = binaries - - if key_blobs: - for key_blob in key_blobs: - self.add_key_blob(key_blob) - - # Just fill up the minimum count of key blobs - while len(self._key_blobs) < self.blobs_min_cnt: - self.add_key_blob( - KeyBlob( - start_addr=0, - end_addr=0, - key=bytes([0] * KeyBlob.KEY_SIZE), - counter_iv=bytes([0] * KeyBlob.CTR_SIZE), - key_flags=0, - zero_fill=bytes([0] * 4), - ) - ) - - @staticmethod - def get_blhost_script_otp_keys( - family: str, otp_master_key: bytes, otfad_key_seed: bytes - ) -> str: - """Create BLHOST script to load fuses needed to run OTFAD with OTP fuses. - - :param family: Device family. - :param otp_master_key: OTP Master Key. - :param otfad_key_seed: OTFAD Key Seed. - :return: BLHOST script that loads the keys into fuses. - """ - database = get_db(family, "latest") - xml_fuses = database.get_file_path( - DatabaseManager.OTFAD, "reg_fuses", default=None - ) - if not xml_fuses: - logger.debug(f"The {family} has no OTFAD fuses definition") - return "" - - fuses = Registers(family, base_endianness=Endianness.LITTLE) - grouped_regs = database.get_list( - DatabaseManager.OTFAD, "grouped_registers", default=None - ) - fuses.load_registers_from_xml(xml_fuses, grouped_regs=grouped_regs) - reg_omk = fuses.find_reg("OTP_MASTER_KEY") - reg_oks = fuses.find_reg("OTFAD_KEK_SEED") - reg_omk.set_value(otp_master_key) - reg_oks.set_value(otfad_key_seed) - ret = ( - "# BLHOST OTFAD keys fuse programming script\n" - f"# Generated by SPSDK {spsdk_version}\n" - f"# Chip: {family}\n\n" - ) - - ret += f"# OTP MASTER KEY(Big Endian): 0x{reg_omk.get_bytes_value(raw=False).hex()}\n\n" - for reg in reg_omk.sub_regs: - ret += f"# {reg.name} fuse.\n" - ret += f"efuse-program-once {reg.offset} 0x{reg.get_bytes_value(raw=True).hex()} --no-verify\n" - - ret += f"\n# OTFAD KEK SEED (Big Endian): 0x{reg_oks.get_bytes_value(raw=True).hex()}\n\n" - for reg in reg_oks.sub_regs: - ret += f"# {reg.name} fuse.\n" - ret += f"efuse-program-once {reg.offset} 0x{reg.get_bytes_value(raw=True).hex()} --no-verify\n" - - return ret - - @staticmethod - def _replace_idx_value(value: str, index: int) -> str: - """Replace index value if provided in the database. - - :param value: value to be replaced f-string containing index - :param index: Index of record to be replaced - :return: value with replaced index - """ - return value.replace("{index}", str(index)) - - def get_blhost_script_otp_kek(self, index: int = 1) -> str: - """Create BLHOST script to load fuses needed to run OTFAD with OTP fuses just for OTFAD key. - - :param index: Index of OTFAD peripheral [1, 2, ..., n]. - :return: BLHOST script that loads the keys into fuses. - """ - if not self.db.get_bool(DatabaseManager.OTFAD, "has_kek_fuses", default=False): - logger.debug(f"The {self.family} has no OTFAD KEK fuses") - return "" - - peripheral_list = self.db.get_list(DatabaseManager.OTFAD, "peripheral_list") - if str(index) not in peripheral_list: - logger.debug(f"The {self.family} has no OTFAD{index} peripheral") - return "" - - filter_out_list = [f"OTFAD{i}" for i in peripheral_list if str(index) != i] - xml_fuses = self.db.get_file_path( - DatabaseManager.OTFAD, "reg_fuses", default=None - ) - if not xml_fuses: - logger.debug(f"The {self.family} has no OTFAD fuses definition") - return "" - - fuses = Registers(self.family, base_endianness=Endianness.LITTLE) - - grouped_regs = self.db.get_list( - DatabaseManager.OTFAD, "grouped_registers", default=None - ) - - fuses.load_registers_from_xml(xml_fuses, filter_out_list, grouped_regs) - - scramble_enabled = ( - self.key_scramble_mask is not None and self.key_scramble_align is not None - ) - - otfad_key_fuse = self._replace_idx_value( - self.db.get_str(DatabaseManager.OTFAD, "otfad_key_fuse"), index - ) - otfad_cfg_fuse = self._replace_idx_value( - self.db.get_str(DatabaseManager.OTFAD, "otfad_cfg_fuse"), index - ) - - fuses.find_reg(otfad_key_fuse).set_value(self.kek) - otfad_cfg = fuses.find_reg(otfad_cfg_fuse) - - try: - otfad_cfg.find_bitfield( - self.db.get_str(DatabaseManager.OTFAD, "otfad_enable_bitfield") - ).set_value(1) - except SPSDKRegsErrorBitfieldNotFound: - logger.debug(f"Bitfield for OTFAD ENABLE not found for {self.family}") - - if scramble_enabled: - scramble_key = self._replace_idx_value( - self.db.get_str(DatabaseManager.OTFAD, "otfad_scramble_key"), index - ) - scramble_align = self._replace_idx_value( - self.db.get_str(DatabaseManager.OTFAD, "otfad_scramble_align_bitfield"), - index, - ) - scramble_align_standalone = self.db.get_bool( - DatabaseManager.OTFAD, "otfad_scramble_align_fuse_standalone" - ) - otfad_cfg.find_bitfield( - self._replace_idx_value( - self.db.get_str( - DatabaseManager.OTFAD, "otfad_scramble_enable_bitfield" - ), - index, - ) - ).set_value(1) - if scramble_align_standalone: - fuses.find_reg(scramble_align).set_value(self.key_scramble_align) - else: - otfad_cfg.find_bitfield(scramble_align).set_value( - self.key_scramble_align - ) - fuses.find_reg(scramble_key).set_value(self.key_scramble_mask) - - ret = ( - f"# BLHOST OTFAD{index} KEK fuses programming script\n" - f"# Generated by SPSDK {spsdk_version}\n" - f"# Chip: {self.family}, peripheral: OTFAD{index} !\n\n" - ) - - ret += f"# OTP KEK (Big Endian): {self.kek.hex()}\n\n" - for reg in fuses.find_reg(otfad_key_fuse).sub_regs: - ret += f"# {reg.name} fuse.\n" - ret += f"efuse-program-once {reg.offset} 0x{reg.get_bytes_value(raw=True).hex()} --no-verify\n" - - ret += f"\n\n# {otfad_cfg.name} fuse.\n" - for bitfield in otfad_cfg.get_bitfields(): - ret += f"# {bitfield.name}: {bitfield.get_enum_value()}\n" - ret += f"efuse-program-once {otfad_cfg.offset} 0x{otfad_cfg.get_bytes_value(raw=True).hex()} --no-verify\n" - - if scramble_enabled: - scramble = fuses.find_reg(scramble_key) - ret += f"\n# {scramble.name} fuse.\n" - ret += f"efuse-program-once {scramble.offset} 0x{scramble.get_bytes_value(raw=True).hex()} --no-verify\n" - if scramble_align_standalone: - scramble_align_reg = fuses.find_reg(scramble_align) - ret += f"\n# {scramble_align_reg.name} fuse.\n" - ret += ( - f"efuse-program-once {scramble_align_reg.offset}" - f" 0x{scramble_align_reg.get_bytes_value(raw=True).hex()} --no-verify\n" - ) - - return ret - - def export_image( - self, - plain_data: bool = False, - swap_bytes: bool = False, - join_sub_images: bool = True, - table_address: int = 0, - ) -> Optional[BinaryImage]: - """Get the OTFAD Key Blob Binary Image representation. - - :param plain_data: Binary representation in plain data format, defaults to False - :param swap_bytes: For some platforms the swap bytes is needed in encrypted format, defaults to False. - :param join_sub_images: If it's True, all the binary sub-images are joined into one, defaults to True. - :param table_address: Absolute address of OTFAD table. - :return: OTFAD key blob data in BinaryImage. - """ - if self.binaries is None: - return None - binaries: BinaryImage = deepcopy(self.binaries) - for binary in binaries.sub_images: - if binary.binary: - binary.binary = align_block( - binary.binary, KeyBlob._ENCRYPTION_BLOCK_SIZE - ) - for segment in binary.sub_images: - if segment.binary: - segment.binary = align_block( - segment.binary, KeyBlob._ENCRYPTION_BLOCK_SIZE - ) - - binaries.validate() - - if not plain_data: - for binary in binaries.sub_images: - if binary.binary: - binary.binary = self.encrypt_image( - binary.binary, - table_address + binary.absolute_address, - swap_bytes, - ) - for segment in binary.sub_images: - if segment.binary: - segment.binary = self.encrypt_image( - segment.binary, - segment.absolute_address + table_address, - swap_bytes, - ) - - if join_sub_images: - binaries.join_images() - binaries.validate() - - return binaries - - def binary_image( - self, - plain_data: bool = False, - data_alignment: int = 16, - otfad_table_name: str = "OTFAD_Table", - ) -> BinaryImage: - """Get the OTFAD Binary Image representation. - - :param plain_data: Binary representation in plain format, defaults to False - :param data_alignment: Alignment of data part key blobs. - :param otfad_table_name: name of the output file that contains OTFAD table - :return: OTFAD in BinaryImage. - """ - otfad = BinaryImage("OTFAD", offset=self.table_address) - # Add mandatory OTFAD table - otfad_table = ( - self.get_key_blobs() - if plain_data - else self.encrypt_key_blobs( - self.kek, - self.key_scramble_mask, - self.key_scramble_align, - self.keyblob_byte_swap_cnt, - ) - ) - otfad.add_image( - BinaryImage( - otfad_table_name, - size=self.key_blob_rec_size * self.blobs_max_cnt, - offset=0, - description=f"OTFAD description table for {self.family}", - binary=otfad_table, - alignment=256, - ) - ) - binaries = self.export_image(table_address=self.table_address) - - if binaries: - binaries.alignment = data_alignment - binaries.validate() - otfad.add_image(binaries) - return otfad - - @staticmethod - def get_supported_families() -> List[str]: - """Get all supported families for AHAB container. - - :return: List of supported families. - """ - return get_families(DatabaseManager.OTFAD) - - @staticmethod - def get_validation_schemas(family: str) -> List[Dict[str, Any]]: - """Get list of validation schemas. - - :param family: Family for which the template should be generated. - :return: Validation list of schemas. - """ - if family not in OtfadNxp.get_supported_families(): - return [] - - database = get_db(family, "latest") - schemas = get_schema_file(DatabaseManager.OTFAD) - family_sch = schemas["otfad_family"] - family_sch["properties"]["family"]["enum"] = OtfadNxp.get_supported_families() - family_sch["properties"]["family"]["template_value"] = family - ret = [family_sch, schemas["otfad_output"], schemas["otfad"]] - additional_schemes = database.get_list( - DatabaseManager.OTFAD, "additional_template", default=[] - ) - ret.extend([schemas[x] for x in additional_schemes]) - return ret - - @staticmethod - def get_validation_schemas_family() -> List[Dict[str, Any]]: - """Get list of validation schemas for family key. - - :return: Validation list of schemas. - """ - schemas = get_schema_file(DatabaseManager.OTFAD) - family_sch = schemas["otfad_family"] - family_sch["properties"]["family"]["enum"] = OtfadNxp.get_supported_families() - return [family_sch] - - @staticmethod - def generate_config_template(family: str) -> Dict[str, Any]: - """Generate OTFAD configuration template. - - :param family: Family for which the template should be generated. - :return: Dictionary of individual templates (key is name of template, value is template itself). - """ - val_schemas = OtfadNxp.get_validation_schemas(family) - database = get_db(family, "latest") - - if val_schemas: - template_note = database.get_str( - DatabaseManager.OTFAD, "additional_template_text", default="" - ) - title = f"On-The-Fly AES decryption Configuration template for {family}." - - yaml_data = CommentedConfig( - title, val_schemas, note=template_note - ).get_template() - - return {f"{family}_otfad": yaml_data} - - return {} - - @staticmethod - def load_from_config( - config: Dict[str, Any], - config_dir: str, - search_paths: Optional[List[str]] = None, - ) -> "OtfadNxp": - """Converts the configuration option into an OTFAD image object. - - "config" content array of containers configurations. - - :param config: array of OTFAD configuration dictionaries. - :param config_dir: directory where the config is located - :param search_paths: List of paths where to search for the file, defaults to None - :return: initialized OTFAD object. - """ - otfad_config: List[Dict[str, Any]] = config["key_blobs"] - family = config["family"] - database = get_db(family, "latest") - kek = load_hex_string( - config["kek"], expected_size=16, search_paths=search_paths - ) - logger.debug(f"Loaded KEK: {kek.hex()}") - table_address = value_to_int(config["otfad_table_address"]) - start_address = min( - [value_to_int(addr["start_address"]) for addr in otfad_config] - ) - - key_scramble_mask = None - key_scramble_align = None - if database.get_bool( - DatabaseManager.OTFAD, "supports_key_scrambling", default=False - ): - if "key_scramble" in config.keys(): - key_scramble = config["key_scramble"] - key_scramble_mask = value_to_int(key_scramble["key_scramble_mask"]) - key_scramble_align = value_to_int(key_scramble["key_scramble_align"]) - - data_blobs: Optional[List[Dict]] = config.get("data_blobs") - binaries = None - if data_blobs: - # pylint: disable-next=nested-min-max - start_address = min( - min([value_to_int(addr["address"]) for addr in data_blobs]), - start_address, - ) - binaries = BinaryImage( - filepath_from_config( - config, - "encrypted_name", - "encrypted_blobs", - config_dir, - config["output_folder"], - ), - offset=start_address - table_address, - ) - for data_blob in data_blobs: - data = load_binary(data_blob["data"], search_paths=search_paths) - address = value_to_int(data_blob["address"]) - - binary = BinaryImage( - os.path.basename(data_blob["data"]), - offset=address - table_address - binaries.offset, - binary=data, - ) - binaries.add_image(binary) - else: - logger.warning("The OTFAD configuration has NOT any data blobs records!") - - otfad = OtfadNxp( - family=family, - kek=kek, - table_address=table_address, - key_scramble_align=key_scramble_align, - key_scramble_mask=key_scramble_mask, - binaries=binaries, - ) - - for i, key_blob_cfg in enumerate(otfad_config): - aes_key = value_to_bytes(key_blob_cfg["aes_key"], byte_cnt=KeyBlob.KEY_SIZE) - aes_ctr = value_to_bytes(key_blob_cfg["aes_ctr"], byte_cnt=KeyBlob.CTR_SIZE) - start_addr = value_to_int(key_blob_cfg["start_address"]) - end_addr = value_to_int(key_blob_cfg["end_address"]) - aes_decryption_enable = key_blob_cfg.get("aes_decryption_enable", True) - valid = key_blob_cfg.get("valid", True) - read_only = key_blob_cfg.get("read_only", True) - flags = 0 - if aes_decryption_enable: - flags |= KeyBlob.KEY_FLAG_ADE - if valid: - flags |= KeyBlob.KEY_FLAG_VLD - if read_only: - flags |= KeyBlob.KEY_FLAG_READ_ONLY - - otfad[i] = KeyBlob( - start_addr=start_addr, - end_addr=end_addr, - key=aes_key, - counter_iv=aes_ctr, - key_flags=flags, - zero_fill=bytes([0] * 4), - ) - - return otfad diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/rot.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/rot.py deleted file mode 100644 index 80858300..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/crypto/rot.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""The module provides support for RoT hash calculation .""" - - -from abc import abstractmethod -from typing import List, Optional, Sequence, Type, Union - -from ...crypto.certificate import Certificate -from ...crypto.keys import PrivateKey, PublicKey -from ...exceptions import SPSDKError -from ...image.ahab.ahab_container import SRKRecord -from ...image.ahab.ahab_container import SRKTable as AhabSrkTable -from ...image.secret import SrkItem -from ...image.secret import SrkTable as HabSrkTable -from ...utils.crypto.rkht import RKHT, RKHTv1, RKHTv21 -from ...utils.database import DatabaseManager, get_db, get_families -from ...utils.misc import load_binary - - -class Rot: - """Root of Trust object providing an abstraction over the RoT hash calculation for multiple device families.""" - - def __init__( - self, - family: str, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """Root of Trust initialization.""" - self.rot_obj = self.get_rot_class(family)( - keys_or_certs=keys_or_certs, password=password, search_paths=search_paths - ) - - def calculate_hash(self) -> bytes: - """Calculate RoT hash.""" - return self.rot_obj.calculate_hash() - - def export(self) -> bytes: - """Export RoT.""" - return self.rot_obj.export() - - @classmethod - def get_supported_families(cls) -> List[str]: - """Get all supported families.""" - return get_families(DatabaseManager.CERT_BLOCK) - - @classmethod - def get_rot_class(cls, family: str) -> Type["RotBase"]: - """Get RoT class.""" - db = get_db(family, "latest") - rot_type = db.get_str(DatabaseManager.CERT_BLOCK, "rot_type") - for subclass in RotBase.__subclasses__(): - if subclass.rot_type == rot_type: - return subclass - raise SPSDKError(f"A ROT type {rot_type} does not exist.") - - -class RotBase: - """Root of Trust base class.""" - - rot_type: Optional[str] = None - - def __init__( - self, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """Rot initialization.""" - self.keys_or_certs = keys_or_certs - self.password = password - self.search_paths = search_paths - - @abstractmethod - def calculate_hash( - self, - ) -> bytes: - """Calculate ROT hash.""" - - @abstractmethod - def export(self) -> bytes: - """Calculate ROT table.""" - - -class RotCertBlockv1(RotBase): - """Root of Trust for certificate block v1 class.""" - - rot_type = "cert_block_1" - - def __init__( - self, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """Rot cert block v1 initialization.""" - super().__init__(keys_or_certs, password, search_paths) - self.rkht = RKHTv1.from_keys( - self.keys_or_certs, self.password, self.search_paths - ) - - def calculate_hash( - self, - ) -> bytes: - """Calculate RoT hash.""" - return self.rkht.rkth() - - def export(self) -> bytes: - """Export RoT.""" - return self.rkht.export() - - -class RotCertBlockv21(RotBase): - """Root of Trust for certificate block v21 class.""" - - rot_type = "cert_block_21" - - def __init__( - self, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """Rot cert block v21 initialization.""" - super().__init__(keys_or_certs, password, search_paths) - self.rkht = RKHTv21.from_keys( - self.keys_or_certs, self.password, self.search_paths - ) - - def calculate_hash( - self, - ) -> bytes: - """Calculate ROT hash.""" - return self.rkht.rkth() - - def export(self) -> bytes: - """Export RoT.""" - return self.rkht.export() - - -class RotSrkTableAhab(RotBase): - """Root of Trust for AHAB SrkTable class.""" - - rot_type = "srk_table_ahab" - - def __init__( - self, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """AHAB SRK table initialization.""" - super().__init__(keys_or_certs, password, search_paths) - self.srk = AhabSrkTable( - [ - SRKRecord(RKHT.convert_key(key, password, search_paths)) - for key in keys_or_certs - ] - ) - self.srk.update_fields() - - def calculate_hash(self) -> bytes: - """Calculate ROT hash.""" - return self.srk.compute_srk_hash() - - def export(self) -> bytes: - """Export RoT.""" - return self.srk.export() - - -class RotSrkTableHab(RotBase): - """Root of Trust for HAB SrkTable class.""" - - rot_type = "srk_table_hab" - - def __init__( - self, - keys_or_certs: Sequence[ - Union[str, bytes, bytearray, PublicKey, PrivateKey, Certificate] - ], - password: Optional[str] = None, - search_paths: Optional[List[str]] = None, - ) -> None: - """HAB SRK table initialization.""" - super().__init__(keys_or_certs, password, search_paths) - self.srk = HabSrkTable() - for certificate in keys_or_certs: - if isinstance(certificate, (str, bytes, bytearray)): - try: - certificate = self._load_certificate(certificate, search_paths) - except SPSDKError as exc: - raise SPSDKError( - "Unable to load certificate. Certificate must be provided for HAB RoT calculation." - ) from exc - if not isinstance(certificate, Certificate): - raise SPSDKError( - "Certificate must be provided for HAB RoT calculation." - ) - item = SrkItem.from_certificate(certificate) - self.srk.append(item) - - def calculate_hash(self) -> bytes: - """Calculate ROT hash.""" - return self.srk.export_fuses() - - def export(self) -> bytes: - """Export RoT.""" - return self.srk.export() - - @classmethod - def _load_certificate( - cls, - certificate: Union[str, bytes, bytearray], - search_paths: Optional[List[str]] = None, - ) -> Certificate: - """Load certificate if certificate provided, or extract public key if private/public key is provided.""" - if isinstance(certificate, str): - certificate = load_binary(certificate, search_paths) - try: - return Certificate.parse(certificate) - except SPSDKError as exc: - raise SPSDKError("Unable to load certificate.") from exc diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/images.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/images.py deleted file mode 100644 index 280b656c..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/images.py +++ /dev/null @@ -1,616 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2022-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Module to keep additional utilities for binary images.""" - -import logging -import math -import os -import re -import textwrap -from typing import TYPE_CHECKING, Any, Dict, List, Optional - -import colorama - -from ..exceptions import SPSDKError, SPSDKOverlapError, SPSDKValueError -from ..utils.database import DatabaseManager -from ..utils.misc import ( - BinaryPattern, - align, - align_block, - find_file, - format_value, - size_fmt, - write_file, -) -from ..utils.schema_validator import CommentedConfig - -if TYPE_CHECKING: - # bincopy will be loaded lazily as needed, this is just to satisfy type-hint checkers - import bincopy - -logger = logging.getLogger(__name__) - - -class ColorPicker: - """Simple class to get each time when ask different color from list.""" - - COLORS = [ - colorama.Fore.LIGHTBLACK_EX, - colorama.Fore.BLUE, - colorama.Fore.GREEN, - colorama.Fore.CYAN, - colorama.Fore.YELLOW, - colorama.Fore.MAGENTA, - colorama.Fore.WHITE, - colorama.Fore.LIGHTBLUE_EX, - colorama.Fore.LIGHTCYAN_EX, - colorama.Fore.LIGHTGREEN_EX, - colorama.Fore.LIGHTMAGENTA_EX, - colorama.Fore.LIGHTWHITE_EX, - colorama.Fore.LIGHTYELLOW_EX, - ] - - def __init__(self) -> None: - """Constructor of ColorPicker.""" - self.index = len(self.COLORS) - - def get_color(self, unwanted_color: Optional[str] = None) -> str: - """Get new color from list. - - :param unwanted_color: Color that should be omitted. - :return: Color - """ - self.index += 1 - if self.index >= len(ColorPicker.COLORS): - self.index = 0 - if unwanted_color and ColorPicker.COLORS[self.index] == unwanted_color: - return self.get_color(unwanted_color) - return ColorPicker.COLORS[self.index] - - -class BinaryImage: - """Binary Image class.""" - - MINIMAL_DRAW_WIDTH = 30 - - def __init__( - self, - name: str, - size: int = 0, - offset: int = 0, - description: Optional[str] = None, - binary: Optional[bytes] = None, - pattern: Optional[BinaryPattern] = None, - alignment: int = 1, - parent: Optional["BinaryImage"] = None, - ) -> None: - """Binary Image class constructor. - - :param name: Name of Image. - :param size: Image size. - :param offset: Image offset in parent image, defaults to 0 - :param description: Text description of image, defaults to None - :param binary: Optional binary content. - :param pattern: Optional binary pattern. - :param alignment: Optional alignment of result image - :param parent: Handle to parent object, defaults to None - """ - self.name = name - self.description = description - self.offset = offset - self._size = align(size, alignment) - self.binary = binary - self.pattern = pattern - self.alignment = alignment - self.parent = parent - - if parent: - assert isinstance(parent, BinaryImage) - self.sub_images: List["BinaryImage"] = [] - - @property - def size(self) -> int: - """Size property.""" - return len(self) - - @size.setter - def size(self, value: int) -> None: - """Size property setter.""" - self._size = align(value, self.alignment) - - def add_image(self, image: "BinaryImage") -> None: - """Add new sub image information. - - :param image: Image object. - """ - image.parent = self - for i, child in enumerate(self.sub_images): - if image.offset < child.offset: - self.sub_images.insert(i, image) - return - self.sub_images.append(image) - - def join_images(self) -> None: - """Join all sub images into main binary block.""" - binary = self.export() - self.sub_images.clear() - self.binary = binary - - @property - def image_name(self) -> str: - """Image name including all parents. - - :return: Full Image name - """ - if self.parent: - return self.parent.image_name + "=>" + self.name - return self.name - - @property - def absolute_address(self) -> int: - """Image absolute address relative to base parent. - - :return: Absolute address relative to base parent - """ - if self.parent: - return self.parent.absolute_address + self.offset - return self.offset - - def aligned_start(self, alignment: int = 4) -> int: - """Returns aligned start address. - - :param alignment: The alignment value, defaults to 4. - :return: Floor alignment address. - """ - return math.floor(self.absolute_address / alignment) * alignment - - def aligned_length(self, alignment: int = 4) -> int: - """Returns aligned length for erasing purposes. - - :param alignment: The alignment value, defaults to 4. - :return: Ceil alignment length. - """ - end_address = self.absolute_address + len(self) - aligned_end = math.ceil(end_address / alignment) * alignment - aligned_len = aligned_end - self.aligned_start(alignment) - return aligned_len - - def __str__(self) -> str: - """Provides information about image. - - :return: String information about Image. - """ - size = len(self) - ret = "" - ret += f"Name: {self.image_name}\n" - ret += f"Starts: {hex(self.absolute_address)}\n" - ret += f"Ends: {hex(self.absolute_address+ size-1)}\n" - ret += f"Size: {self._get_size_line(size)}\n" - ret += f"Alignment: {size_fmt(self.alignment, use_kibibyte=False)}\n" - if self.pattern: - ret += f"Pattern:{self.pattern.pattern}\n" - if self.description: - ret += self.description + "\n" - return ret - - def validate(self) -> None: - """Validate if the images doesn't overlaps each other.""" - if self.offset < 0: - raise SPSDKValueError( - f"Image offset of {self.image_name} cannot be in negative numbers." - ) - if len(self) < 0: - raise SPSDKValueError( - f"Image size of {self.image_name} cannot be in negative numbers." - ) - for image in self.sub_images: - image.validate() - begin = image.offset - end = begin + len(image) - 1 - # Check if it fits inside the parent image - if end >= len(self): - raise SPSDKOverlapError( - f"The image {image.name} doesn't fit into {self.name} parent image." - ) - # Check if it doesn't overlap any other sibling image - for sibling in self.sub_images: - if sibling != image: - sibling_begin = sibling.offset - sibling_end = sibling_begin + len(sibling) - 1 - if end < sibling_begin or begin > sibling_end: - continue - - raise SPSDKOverlapError( - f"The image overlap error:\n" - f"{str(image)}\n" - "overlaps the:\n" - f"{str(sibling)}\n" - ) - - def _get_size_line(self, size: int) -> str: - """Get string of size line. - - :param size: Size in bytes - :return: Formatted size line. - """ - if size >= 1024: - real_size = ",".join(re.findall(".{1,3}", (str(len(self)))[::-1]))[::-1] - return f"Size: {size_fmt(len(self), False)}; {real_size} B" - - return f"Size: {size_fmt(len(self), False)}" - - def get_min_draw_width(self, include_sub_images: bool = True) -> int: - """Get minimal width of table for draw function. - - :param include_sub_images: Include also sub images into, defaults to True - :return: Minimal width in characters. - """ - widths = [ - self.MINIMAL_DRAW_WIDTH, - len(f"+==-0x0000_0000= {self.name} =+"), - len(f"|{self._get_size_line(self.size)}|"), - ] - if include_sub_images: - for child in self.sub_images: - widths.append( - child.get_min_draw_width() + 2 - ) # +2 means add vertical borders - return max(widths) - - def draw( - self, - include_sub_images: bool = True, - width: int = 0, - color: str = "", - no_color: bool = False, - ) -> str: - # fmt: off - """Draw the image into the ASCII graphics. - - :param include_sub_images: Include also sub images into, defaults to True - :param width: Fixed width of table, 0 means autosize. - :param color: Color of this block, None means automatic color. - :param no_color: Disable adding colors into output. - :raises SPSDKValueError: In case of invalid width. - :return: ASCII art representation of image. - """ - # +==0x0000_0000==Title1===============+ - # | Size: 2048B | - # | Description1 | - # | Description1 2nd line | - # |+==0x0000_0000==Title11============+| - # || Size: 512B || - # || Description11 || - # || Description11 2nd line || - # |+==0x0000_01FF=====================+| - # | | - # |+==0x0000_0210==Title12============+| - # || Size: 512B || - # || Description12 || - # || Description12 2nd line || - # |+==0x0000_041F=====================+| - # +==0x0000_07FF=======================+ - # fmt: on - def _get_centered_line(text: str) -> str: - text_len = len(text) - spaces = width - text_len - 2 - assert spaces >= 0, "Binary Image Draw: Center line is longer than width" - padding_l = int(spaces / 2) - padding_r = int(spaces - padding_l) - return color + f"|{' '*padding_l}{text}{' '*padding_r}|\n" - - def wrap_block(inner: str) -> str: - wrapped_block = "" - lines = inner.splitlines(keepends=False) - for line in lines: - wrapped_block += color + "|" + line + color + "|\n" - return wrapped_block - - if no_color: - color = "" - else: - color_picker = ColorPicker() - try: - self.validate() - color = color or color_picker.get_color() - except SPSDKError: - color = colorama.Fore.RED - - block = "" if self.parent else "\n" - min_width = self.get_min_draw_width(include_sub_images) - if not width and self.parent is None: - width = min_width - - if width < min_width: - raise SPSDKValueError( - f"Binary Image Draw: Width is to short ({width} < minimal width: {min_width})" - ) - - # - Title line - header = f"+=={format_value(self.absolute_address, 32)}= {self.name} =" - block += color + f"{header}{'='*(width-len(header)-1)}+\n" - # - Size - block += _get_centered_line(self._get_size_line(len(self))) - # - Description - if self.description: - for line in textwrap.wrap( - self.description, width=width - 2, fix_sentence_endings=True - ): - block += _get_centered_line(line) - # - Pattern - if self.pattern: - block += _get_centered_line(f"Pattern: {self.pattern.pattern}") - # - Inner blocks - if include_sub_images: - next_free_space = 0 - for child in self.sub_images: - # If the images doesn't comes one by one place empty line - if child.offset != next_free_space: - block += _get_centered_line( - f"Gap: {size_fmt(child.offset-next_free_space, False)}" - ) - next_free_space = child.offset + len(child) - inner_block = child.draw( - include_sub_images=include_sub_images, - width=width - 2, - color="" if no_color else color_picker.get_color(color), - no_color=no_color, - ) - block += wrap_block(inner_block) - - # - Closing line - footer = f"+=={format_value(self.absolute_address + len(self) - 1, 32)}==" - block += color + f"{footer}{'='*(width-len(footer)-1)}+\n" - - if self.parent is None: - block += "\n" + "" if no_color else colorama.Fore.RESET - return block - - def update_offsets(self) -> None: - """Update offsets from the sub images into main offset value begin offsets.""" - offsets = [] - for image in self.sub_images: - offsets.append(image.offset) - - min_offset = min(offsets) - for image in self.sub_images: - image.offset -= min_offset - self.offset += min_offset - - def __len__(self) -> int: - """Get length of image. - - If internal member size is not set(is zero) the size is computed from sub images. - :return: Size of image. - """ - if self._size: - return self._size - max_size = len(self.binary) if self.binary else 0 - for image in self.sub_images: - size = image.offset + len(image) - max_size = max(size, max_size) - return align(max_size, self.alignment) - - def export(self) -> bytes: - """Export represented binary image. - - :return: Byte array of binary image. - """ - if self.binary and len(self) == len(self.binary) and len(self.sub_images) == 0: - return self.binary - - if self.pattern: - ret = bytearray(self.pattern.get_block(len(self))) - else: - ret = bytearray(len(self)) - - if self.binary: - binary_view = memoryview(self.binary) - ret[: len(self.binary)] = binary_view - - for image in self.sub_images: - image_data = image.export() - ret_slice = memoryview(ret)[image.offset : image.offset + len(image_data)] - image_data_view = memoryview(image_data) - ret_slice[:] = image_data_view - - return align_block(ret, self.alignment, self.pattern) - - @staticmethod - def get_validation_schemas() -> List[Dict[str, Any]]: - """Get validation schemas list to check a supported configuration. - - :return: Validation schemas. - """ - return [DatabaseManager().db.get_schema_file("binary")] - - @staticmethod - def load_from_config( - config: Dict[str, Any], search_paths: Optional[List[str]] = None - ) -> "BinaryImage": - """Converts the configuration option into an Binary Image object. - - :param config: Description of binary image. - :param search_paths: List of paths where to search for the file, defaults to None - :return: Initialized Binary Image. - """ - name = config.get("name", "Base Image") - size = config.get("size", 0) - pattern = BinaryPattern(config.get("pattern", "zeros")) - alignment = config.get("alignment", 1) - ret = BinaryImage(name=name, size=size, pattern=pattern, alignment=alignment) - regions = config.get("regions") - if regions: - for i, region in enumerate(regions): - binary_file: Dict = region.get("binary_file") - if binary_file: - offset = binary_file.get( - "offset", ret.aligned_length(ret.alignment) - ) - name = binary_file.get("name", binary_file["path"]) - ret.add_image( - BinaryImage.load_binary_image( - binary_file["path"], - name=name, - offset=offset, - pattern=pattern, - search_paths=search_paths, - ) - ) - binary_block: Dict = region.get("binary_block") - if binary_block: - size = binary_block["size"] - offset = binary_block.get( - "offset", ret.aligned_length(ret.alignment) - ) - name = binary_block.get("name", f"Binary block(#{i})") - pattern = BinaryPattern(binary_block["pattern"]) - ret.add_image(BinaryImage(name, size, offset, pattern=pattern)) - return ret - - def save_binary_image( - self, - path: str, - file_format: str = "BIN", - ) -> None: - # pylint: disable=missing-param-doc - """Save binary data file. - - :param path: Path to the file. - :param file_format: Format of saved file ('BIN', 'HEX', 'S19'), defaults to 'BIN'. - :raises SPSDKValueError: The file format is invalid. - """ - file_format = file_format.upper() - if file_format.upper() not in ("BIN", "HEX", "S19"): - raise SPSDKValueError(f"Invalid input file format: {file_format}") - - if file_format == "BIN": - write_file(self.export(), path, mode="wb") - return - - def add_into_binary(bin_image: BinaryImage) -> None: - if bin_image.pattern: - bin_file.add_binary( - bin_image.pattern.get_block(len(bin_image)), - address=bin_image.absolute_address, - overwrite=True, - ) - - if bin_image.binary: - bin_file.add_binary( - bin_image.binary, address=bin_image.absolute_address, overwrite=True - ) - - for sub_image in bin_image.sub_images: - add_into_binary(sub_image) - - # import bincopy only if needed to save startup time - import bincopy # pylint: disable=import-outside-toplevel - - bin_file = bincopy.BinFile() - add_into_binary(self) - - if file_format == "HEX": - write_file(bin_file.as_ihex(), path) - return - - # And final supported format is....... Yes, S record from MOTOROLA - write_file(bin_file.as_srec(), path) - - @staticmethod - def generate_config_template() -> str: - """Generate configuration template. - - :return: Template to create binary merge.. - """ - return CommentedConfig( - "Binary Image Configuration template.", BinaryImage.get_validation_schemas() - ).get_template() - - @staticmethod - def load_binary_image( - path: str, - name: Optional[str] = None, - size: int = 0, - offset: int = 0, - description: Optional[str] = None, - pattern: Optional[BinaryPattern] = None, - search_paths: Optional[List[str]] = None, - alignment: int = 1, - load_bin: bool = True, - ) -> "BinaryImage": - # pylint: disable=missing-param-doc - r"""Load binary data file. - - Supported formats are ELF, HEX, SREC and plain binary - - :param path: Path to the file. - :param name: Name of Image, defaults to file name. - :param size: Image size, defaults to 0. - :param offset: Image offset in parent image, defaults to 0 - :param description: Text description of image, defaults to None - :param pattern: Optional binary pattern. - :param search_paths: List of paths where to search for the file, defaults to None - :param alignment: Optional alignment of result image - :param load_bin: Load as binary in case of every other format load fails - :raises SPSDKError: The binary file cannot be loaded. - :return: Binary data represented in BinaryImage class. - """ - path = find_file(path, search_paths=search_paths) - try: - with open(path, "rb") as f: - data = f.read(4) - except Exception as e: - raise SPSDKError(f"Error loading file: {str(e)}") from e - - # import bincopy only if needed to save startup time - import bincopy # pylint: disable=import-outside-toplevel - - bin_file = bincopy.BinFile() - try: - if data == b"\x7fELF": - bin_file.add_elf_file(path) - else: - try: - bin_file.add_file(path) - except (UnicodeDecodeError, bincopy.UnsupportedFileFormatError) as e: - if load_bin: - bin_file.add_binary_file(path) - else: - raise SPSDKError("Cannot load file as ELF, HEX or SREC") from e - except Exception as e: - raise SPSDKError(f"Error loading file: {str(e)}") from e - - img_name = name or os.path.basename(path) - img_size = size or 0 - img_descr = description or f"The image loaded from: {path} ." - bin_image = BinaryImage( - name=img_name, - size=img_size, - offset=offset, - description=img_descr, - pattern=pattern, - alignment=alignment, - ) - if len(bin_file.segments) == 0: - raise SPSDKError(f"Load of {path} failed, can't be decoded.") - - for i, segment in enumerate(bin_file.segments): - bin_image.add_image( - BinaryImage( - name=f"Segment {i}", - size=len(segment.data), - offset=segment.address, - pattern=pattern, - binary=segment.data, - parent=bin_image, - alignment=alignment, - ) - ) - # Optimize offsets in image - bin_image.update_offsets() - return bin_image diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/sdio_device.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/sdio_device.py deleted file mode 100644 index a5112abb..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/sdio_device.py +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Low level sdio device.""" -import os -import time -from io import FileIO -from typing import List, Optional - -from typing_extensions import Self - -from ....exceptions import SPSDKConnectionError, SPSDKError -from ....utils.exceptions import SPSDKTimeoutError -from ....utils.interfaces.device.base import DeviceBase, logger -from ....utils.misc import Timeout - - -class SdioDevice(DeviceBase): - """SDIO device class.""" - - DEFAULT_TIMEOUT = 2000 - - def __init__( - self, - path: Optional[str] = None, - timeout: int = DEFAULT_TIMEOUT, - ) -> None: - """Initialize the SDIO interface object. - - :raises McuBootConnectionError: when the path is empty - """ - self._opened = False - # Temporarily use hard code until there is a way to retrive VID/PID - self.vid = 0x0471 - self.pid = 0x0209 - self._timeout = timeout - if path is None: - raise SPSDKConnectionError("No SDIO device path") - self.path = path - self.is_blocking = False - self.device: Optional[FileIO] = None - - @property - def timeout(self) -> int: - """Timeout property.""" - return self._timeout - - @timeout.setter - def timeout(self, value: int) -> None: - """Timeout property setter.""" - self._timeout = value - - @property - def is_opened(self) -> bool: - """Indicates whether device is open. - - :return: True if device is open, False othervise. - """ - return self.device is not None and self._opened - - def open(self) -> None: - """Open the interface with non-blocking mode. - - :raises McuBootError: if non-blocking mode is not available - :raises SPSDKError: if trying to open in non-blocking mode on non-linux os - :raises SPSDKConnectionError: if no device is available - :raises SPSDKConnectionError: if the device can not be opened - """ - logger.debug("Opening the sdio device.") - if not self._opened: - try: - self.device = open(self.path, "rb+", buffering=0) - if self.device is None: - raise SPSDKConnectionError("No device available") - if not self.is_blocking: - if not hasattr(os, "set_blocking"): - raise SPSDKError( - "Opening in non-blocking mode is available only on Linux" - ) - # pylint: disable=no-member # this is available only on Unix - os.set_blocking(self.device.fileno(), False) - self._opened = True - except Exception as error: - raise SPSDKConnectionError( - f"Unable to open device '{self.path}' VID={self.vid} PID={self.pid}" - ) from error - - def close(self) -> None: - """Close the interface. - - :raises SPSDKConnectionError: if no device is available - :raises SPSDKConnectionError: if the device can not be opened - """ - logger.debug("Closing the sdio Interface.") - if not self.device: - raise SPSDKConnectionError("No device available") - if self._opened: - try: - self.device.close() - self._opened = False - except Exception as error: - raise SPSDKConnectionError( - f"Unable to close device '{self.path}' VID={self.vid} PID={self.pid}" - ) from error - - def read(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises SPSDKTimeoutError: Time-out - :raises SPSDKConnectionError: When device was not open for reading - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for reading") - _read = self._read_blocking if self.is_blocking else self._read_non_blocking - data = _read(length=length, timeout=timeout) - if not data: - raise SPSDKTimeoutError() - logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") - return data - - def _read_blocking(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device in blocking mode. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises SPSDKConnectionError: When reading data from device fails - :raises SPSDKConnectionError: Raises if device is not opened for reading - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for writing") - logger.debug("Reading with blocking mode.") - try: - return self.device.read(length) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - def _read_non_blocking(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device in non-blocking mode. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises TimeoutError: When timeout occurs - :raises SPSDKConnectionError: When reading data from device fails - :raises SPSDKConnectionError: Raises if device is not opened for reading - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for reading") - logger.debug("Reading with non-blocking mode.") - has_data = 0 - no_data_continuous = 0 - - data = bytearray() - _timeout = Timeout(timeout or self.timeout, "ms") - while len(data) < length: - try: - buf = self.device.read(length) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - if buf is None: - time.sleep(0.05) # delay for access device - if has_data != 0: - no_data_continuous = no_data_continuous + 1 - else: - data.extend(buf) - logger.debug("expend buf") - has_data = has_data + 1 - no_data_continuous = 0 - - if no_data_continuous > 5: - break - if _timeout.overflow(): - logger.debug("SDIO interface : read timeout") - break - return bytes(data) - - def write(self, data: bytes, timeout: Optional[int] = None) -> None: - """Send data to device with non-blocking mode. - - :param data: Data to send - :param timeout: Write timeout - :raises SPSDKConnectionError: Raises an error if device is not available - :raises SPSDKConnectionError: When sending the data fails - :raises TimeoutError: When timeout occurs - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for writing.") - logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") - _write = self._write_blocking if self.is_blocking else self._write_non_blocking - _write(data=data, timeout=timeout) - - def _write_blocking(self, data: bytes, timeout: Optional[int] = None) -> None: - """Write data to device in blocking mode. - - :param data: Data to be written - :param timeout: Write timeout - - :raises SPSDKConnectionError: When writing data to device fails - :raises SPSDKConnectionError: Raises if device is not opened for writing - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for writing") - logger.debug("Writing in blocking mode") - try: - self.device.write(data) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - def _write_non_blocking(self, data: bytes, timeout: Optional[int] = None) -> None: - """Write data to device in non-blocking mode. - - :param data: Data to be written - :param timeout: Write timeout - - :raises SPSDKConnectionError: When writing data to device fails - :raises SPSDKConnectionError: Raises if device is not opened for writing - """ - if not self.device or not self.is_opened: - raise SPSDKConnectionError("Device is not opened for writing") - logger.debug("Writing in non-blocking mode") - tx_len = len(data) - _timeout = Timeout(timeout or self.timeout, "ms") - while tx_len > 0: - try: - wr_count = self.device.write(data) - time.sleep(0.05) - data = data[wr_count:] - tx_len -= wr_count - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if _timeout.overflow(): - raise SPSDKTimeoutError() - - def __str__(self) -> str: - """Return information about the SDIO interface.""" - return f"(0x{self.vid:04X}, 0x{self.pid:04X})" - - @classmethod - def scan( - cls, - device_path: str, - timeout: Optional[int] = None, - ) -> List[Self]: - """Scan connected SDIO devices. - - :param device_path: device path string - :param timeout: default read/write timeout - :return: matched SDIO device - """ - if device_path is None: - logger.debug("No sdio path has been defined.") - devices = [] - try: - logger.debug(f"Checking path: {device_path}") - device = cls(path=device_path, timeout=timeout or cls.DEFAULT_TIMEOUT) - device.open() - device.close() - devices = [device] if device else [] - except Exception as e: # pylint: disable=broad-except - logger.debug(f"{type(e).__name__}: {e}") - devices = [] - return devices diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/serial_device.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/serial_device.py deleted file mode 100644 index 0237cce2..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/serial_device.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2023-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Low level serial device.""" -import logging -from typing import List, Optional - -from serial import Serial, SerialException, SerialTimeoutException -from serial.tools.list_ports import comports -from typing_extensions import Self - -from ....exceptions import SPSDKConnectionError -from ....utils.exceptions import SPSDKTimeoutError -from ....utils.interfaces.device.base import DeviceBase - -logger = logging.getLogger(__name__) - - -class SerialDevice(DeviceBase): - """Serial device class.""" - - default_baudrate = 115200 - default_timeout = 5000 - - def __init__( - self, - port: Optional[str] = None, - timeout: int = default_timeout, - baudrate: int = default_baudrate, - ): - """Initialize the UART interface. - - :param port: name of the serial port, defaults to None - :param baudrate: speed of the UART interface, defaults to 115200 - :param timeout: read/write timeout in milliseconds, defaults to 1000 - :raises SPSDKConnectionError: when there is no port available - """ - super().__init__() - self._timeout = timeout - try: - timeout_s = timeout / 1000 - self._device = Serial( - port=port, timeout=timeout_s, write_timeout=timeout_s, baudrate=baudrate - ) - self.expect_status = True - except SerialException as se: - logger.debug(f"Exception occurred during device opening: {se}") - self.expect_status = False - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - @property - def timeout(self) -> int: - """Timeout property.""" - return self._timeout - - @timeout.setter - def timeout(self, value: int) -> None: - """Timeout property setter.""" - self._timeout = value - self._device.timeout = value / 1000 - self._device.write_timeout = value / 1000 - - @property - def is_opened(self) -> bool: - """Indicates whether device is open. - - :return: True if device is open, False otherwise. - """ - if self.expect_status == False: - return False - else: - return self._device.is_open - - def open(self) -> None: - """Open the UART interface. - - :raises SPSDKConnectionError: when opening device fails - """ - if not self.is_opened: - try: - self._device.open() - except Exception as e: - self.close() - raise SPSDKConnectionError(str(e)) from e - - def close(self) -> None: - """Close the UART interface. - - :raises SPSDKConnectionError: when closing device fails - """ - if self.is_opened: - try: - self._device.reset_input_buffer() - self._device.reset_output_buffer() - self._device.close() - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - def read(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises SPSDKTimeoutError: Time-out - :raises SPSDKConnectionError: When reading data from device fails - """ - if not self.is_opened: - raise SPSDKConnectionError("Device is not opened for reading") - try: - data = self._device.read(length) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if not data: - raise SPSDKTimeoutError() - logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") - return data - - def write(self, data: bytes, timeout: Optional[int] = None) -> None: - """Send data to device. - - :param data: Data to send - :param timeout: Write timeout - :raises SPSDKTimeoutError: when sending of data times-out - :raises SPSDKConnectionError: when send data to device fails - """ - if not self.is_opened: - raise SPSDKConnectionError("Device is not opened for reading") - logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") - try: - self._device.reset_input_buffer() - self._device.reset_output_buffer() - self._device.write(data) - self._device.flush() - except SerialTimeoutException as e: - raise SPSDKTimeoutError( - f"Write timeout error. The timeout is set to {self._device.write_timeout} s. Consider increasing it." - ) from e - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - def __str__(self) -> str: - """Return information about the UART interface. - - :return: information about the UART interface - :raises SPSDKConnectionError: when information can not be collected from device - """ - try: - return self._device.port - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - - @classmethod - def scan( - cls, - port: Optional[str] = None, - baudrate: Optional[int] = None, - timeout: Optional[int] = None, - ) -> List[Self]: - """Scan connected serial ports. - - Returns list of serial ports with devices that respond to PING command. - If 'port' is specified, only that serial port is checked - If no devices are found, return an empty list. - - :param port: name of preferred serial port, defaults to None - :param baudrate: speed of the UART interface, defaults to 56700 - :param timeout: timeout in milliseconds, defaults to 5000 - :return: list of interfaces responding to the PING command - """ - baudrate = baudrate or cls.default_baudrate - timeout = timeout or 5000 - if port: - device = cls._check_port(port, baudrate, timeout) - devices = [device] if device else [] - else: - all_ports = [ - cls._check_port(comport.device, baudrate, timeout) - for comport in comports(include_links=True) - ] - devices = list(filter(None, all_ports)) - return devices - - @classmethod - def _check_port(cls, port: str, baudrate: int, timeout: int) -> Optional[Self]: - """Check if device on comport 'port' responds to PING command. - - :param port: name of port to check - :param baudrate: speed of the UART interface, defaults to 56700 - :param timeout: timeout in milliseconds - :return: None if device doesn't respond to PING, instance of Interface if it does - """ - try: - logger.debug( - f"Checking port: {port}, baudrate: {baudrate}, timeout: {timeout}" - ) - device = cls(port=port, baudrate=baudrate, timeout=timeout) - device.open() - device.close() - return device - except Exception as e: # pylint: disable=broad-except - logger.debug(f"{type(e).__name__}: {e}") - return None diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/usbsio_device.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/usbsio_device.py deleted file mode 100644 index 38b48c6f..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/device/usbsio_device.py +++ /dev/null @@ -1,460 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright (c) 2019-2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Low level usbsio device.""" -import logging -import re -from dataclasses import dataclass -from typing import List, Optional, Union - -import libusbsio -from libusbsio.libusbsio import LIBUSBSIO -from typing_extensions import Self - -from ....exceptions import SPSDKConnectionError, SPSDKError, SPSDKValueError -from ....utils.exceptions import SPSDKTimeoutError -from ....utils.interfaces.device.base import DeviceBase -from ....utils.misc import value_to_int -from ....utils.usbfilter import USBDeviceFilter - -logger = logging.getLogger(__name__) - - -@dataclass -class ScanArgs: - """Scan arguments dataclass.""" - - config: str - - @classmethod - def parse(cls, params: str) -> Self: - """Parse given scanning parameters into ScanArgs class. - - :param params: Parameters as a string - """ - return cls(config=params) - - -class UsbSioDevice(DeviceBase): - """USBSIO device class.""" - - def __init__( - self, dev: int = 0, config: Optional[str] = None, timeout: int = 5000 - ) -> None: - """Initialize the Interface object. - - :param dev: device index to be used, default is set to 0 - :param config: configuration string identifying spi or i2c SIO interface - :param timeout: read timeout in milliseconds, defaults to 5000 - :raises SPSDKError: When LIBUSBSIO device is not opened. - """ - # device is the LIBUSBSIO.PORT instance (LIBUSBSIO.SPI or LIBUSBSIO.I2C class) - self.port: Optional[Union[LIBUSBSIO.SPI, LIBUSBSIO.I2C]] = None - - # work with the global LIBUSBSIO instance - self.dev_ix = dev - self.sio = self._get_usbsio() - self._timeout = timeout - - # store USBSIO configuration and version - self.config = config - - @property - def timeout(self) -> int: - """Timeout property.""" - return self._timeout - - @timeout.setter - def timeout(self, value: int) -> None: - """Timeout property setter.""" - self._timeout = value - - @property - def is_opened(self) -> bool: - """Indicates whether device is open. - - :return: True if device is open, False othervise. - """ - return bool(self.port) - - def close(self) -> None: - """Close the interface.""" - if self.port: - self.port.Close() - self.port = None - self.sio.Close() - # re-init the libusb to prepare it for next open - self.sio.GetNumPorts() - - def __str__(self) -> str: - """Return string containing information about the interface.""" - class_name = self.__class__.__name__ - config = f":'{self.config}'" if self.config else "" - return f"libusbsio interface ({class_name}){config}" - - @staticmethod - def get_interface_cfg(config: str, interface: str) -> str: - """Return part of interface config. - - :param config: Full config of LIBUSBSIO - :param interface: Name of interface to find. - :return: Part with interface config. - """ - i = config.rfind(interface) - if i < 0: - return "" - return config[i:] - - @staticmethod - def _get_usbsio() -> LIBUSBSIO: - """Wraps getting USBSIO library to raise SPSDK errors in case of problem. - - :return: LIBUSBSIO object - :raises SPSDKError: When libusbsio library error or if no bridge device found - """ - try: - # get the global singleton instance of LIBUSBSIO library - libusbsio_logger = logging.getLogger("libusbsio") - return libusbsio.usbsio(loglevel=libusbsio_logger.getEffectiveLevel()) - except libusbsio.LIBUSBSIO_Exception as e: - raise SPSDKError(f"Error in libusbsio interface: {e}") from e - except Exception as e: - raise SPSDKError(str(e)) from e - - @classmethod - def scan( - cls, config: Optional[str] = None, timeout: int = 5000 - ) -> List[Union["UsbSioSPIDevice", "UsbSioI2CDevice"]]: - """Scan connected USB-SIO bridge devices. - - :param config: Configuration string identifying spi or i2c SIO interface - and could filter out USB devices - :param timeout: Read timeout in milliseconds, defaults to 5000 - :return: List of matching UsbSio devices - :raises SPSDKError: When libusbsio library error or if no bridge device found - :raises SPSDKValueError: Invalid configuration detected. - """ - cfg = config.split(",") if config else [] - re_spi = re.compile(r"^spi(?P\d*)") - re_i2c = re.compile(r"^i2c(?P\d*)") - spi = None - i2c = None - for cfg_part in cfg: - match_i2c = re_i2c.match(cfg_part.lower()) - if match_i2c: - i2c = value_to_int(match_i2c.group("index"), 0) - match_spi = re_spi.match(cfg_part.lower()) - if match_spi: - spi = value_to_int(match_spi.group("index"), 0) - if i2c is not None and spi is not None: - raise SPSDKValueError( - f"Cannot be specified spi and i2c together in configuration: {cfg}" - ) - intf_specified = i2c is not None or spi is not None - - port_indexes = cls.get_usbsio_devices(config) - sio = cls._get_usbsio() - devices: List[Union["UsbSioSPIDevice", "UsbSioI2CDevice"]] = [] - for port in port_indexes: - if not sio.Open(port): - raise SPSDKError(f"Cannot open libusbsio bridge {port}.") - i2c_ports = sio.GetNumI2CPorts() - if i2c_ports: - if i2c is not None: - devices.append( - UsbSioI2CDevice( - dev=port, port=i2c, config=config, timeout=timeout - ) - ) - elif not intf_specified: - devices.extend( - [ - UsbSioI2CDevice(dev=port, port=p, timeout=timeout) - for p in range(i2c_ports) - ] - ) - spi_ports = sio.GetNumSPIPorts() - if spi_ports: - if spi is not None: - devices.append( - UsbSioSPIDevice( - dev=port, port=spi, config=config, timeout=timeout - ) - ) - elif not intf_specified: - devices.extend( - [ - UsbSioSPIDevice(dev=port, port=p, timeout=timeout) - for p in range(spi_ports) - ] - ) - if sio.Close() < 0: - raise SPSDKError(f"Cannot close libusbsio bridge {port}.") - # re-init the libusb to prepare it for next open - sio.GetNumPorts() - return devices - - @classmethod - def get_usbsio_devices(cls, config: Optional[str] = None) -> List[int]: - """Returns list of ports indexes of USBSIO devices. - - It could be filtered by standard SPSDK USB filters. - - :param config: Could contain USB filter configuration, defaults to None - :return: List of port indexes of founded USBSIO device - """ - - def _filter_usb(sio: LIBUSBSIO, ports: List[int], flt: str) -> List[int]: - """Filter the LIBUSBSIO device. - - :param sio: LIBUSBSIO instance. - :param ports: Input list of LIBUSBSIO available ports. - :param flt: Filter string (PATH, PID/VID, SERIAL_NUMBER) - :raises SPSDKError: When libusbsio library error or if no bridge device found - :return: List with selected device, empty list otherwise. - """ - usb_filter = USBDeviceFilter(flt.casefold()) - port_indexes = [] - for port in ports: - info = sio.GetDeviceInfo(port) - if not info: - raise SPSDKError( - f"Cannot retrive information from LIBUSBSIO device {port}." - ) - dev_info = { - "vendor_id": info.vendor_id, - "product_id": info.product_id, - "serial_number": info.serial_number, - "path": info.path, - } - if usb_filter.compare(dev_info): - port_indexes.append(port) - break - return port_indexes - - cfg = config.split(",") if config else [] - port_indexes = [] - - sio = UsbSioDevice._get_usbsio() - # it may already be open (?), in that case, just close it - We are scan function! - if sio.IsOpen(): - sio.Close() - - port_indexes.extend(list(range(sio.GetNumPorts()))) - - # filter out the USB devices - if cfg and cfg[0] == "usb": - port_indexes = _filter_usb(sio, port_indexes, cfg[1]) - - return port_indexes - - -class UsbSioSPIDevice(UsbSioDevice): - """USBSIO SPI interface.""" - - def __init__( - self, - config: Optional[str] = None, - dev: int = 0, - port: int = 0, - ssel_port: int = 0, - ssel_pin: int = 15, - speed_khz: int = 1000, - cpol: int = 1, - cpha: int = 1, - timeout: int = 5000, - ) -> None: - """Initialize the UsbSioSPI Interface object. - - :param config: configuration string passed from command line - :param dev: device index to be used, default is set to 0 - :param port: default SPI port to be used, typically 0 as only one port is supported by LPCLink2/MCULink - :param ssel_port: bridge GPIO port used to drive SPI SSEL signal - :param ssel_pin: bridge GPIO pin used to drive SPI SSEL signal - :param speed_khz: SPI clock speed in kHz - :param cpol: SPI clock polarity mode - :param cpha: SPI clock phase mode - :param timeout: read timeout in milliseconds, defaults to 5000 - :raises SPSDKError: When port configuration cannot be parsed - """ - super().__init__(dev=dev, config=config, timeout=timeout) - - # default configuration taken from parameters (and their default values) - self.spi_port = port - self.spi_sselport = ssel_port - self.spi_sselpin = ssel_pin - self.spi_speed_khz = speed_khz - self.spi_cpol = cpol - self.spi_cpha = cpha - - # values can be also overridden by a configuration string - if config: - # config format: spi[,,,,,] - cfg = self.get_interface_cfg(config, "spi").split(",") - try: - self.spi_sselport = int(cfg[1], 0) - self.spi_sselpin = int(cfg[2], 0) - self.spi_speed_khz = int(cfg[3], 0) - self.spi_cpol = int(cfg[4], 0) - self.spi_cpha = int(cfg[5], 0) - except IndexError: - pass - except Exception as e: - raise SPSDKError( - "Cannot parse lpcusbsio SPI parameters.\n" - "Expected: spi[,,,,,]\n" - f"Given: {config}" - ) from e - - def open(self) -> None: - """Open the interface.""" - if not self.sio.IsOpen(): - self.sio.Open(self.dev_ix) - - self.port: LIBUSBSIO.SPI = self.sio.SPI_Open( - portNum=self.spi_port, - busSpeed=self.spi_speed_khz * 1000, - cpol=self.spi_cpol, - cpha=self.spi_cpha, - ) - if not self.port: - raise SPSDKError("Cannot open lpcusbsio SPI interface.\n") - - def read(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises SPSDKConnectionError: When reading data from device fails - :raises TimeoutError: When no data received - """ - try: - (data, result) = self.port.Transfer( - devSelectPort=self.spi_sselport, - devSelectPin=self.spi_sselpin, - txData=None, - size=length, - ) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if result < 0 or not data: - raise SPSDKTimeoutError() - logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") - return data - - def write(self, data: bytes, timeout: Optional[int] = None) -> None: - """Send data to device. - - :param data: Data to send - :param timeout: Write timeout - :raises SPSDKConnectionError: When sending the data fails - :raises SPSDKTimeoutError: When data could not be written - """ - logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") - try: - (dummy, result) = self.port.Transfer( - devSelectPort=self.spi_sselport, - devSelectPin=self.spi_sselpin, - txData=data, - ) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if result < 0: - raise SPSDKTimeoutError() - - -class UsbSioI2CDevice(UsbSioDevice): - """USBSIO I2C interface.""" - - def __init__( - self, - config: Optional[str] = None, - dev: int = 0, - port: int = 0, - address: int = 0x10, - speed_khz: int = 100, - timeout: int = 5000, - ) -> None: - """Initialize the UsbSioI2C Interface object. - - :param config: configuration string passed from command line - :param dev: device index to be used, default is set to 0 - :param port: default I2C port to be used, typically 0 as only one port is supported by LPCLink2/MCULink - :param address: I2C target device address - :param speed_khz: I2C clock speed in kHz - :param timeout: read timeout in milliseconds, defaults to 5000 - :raises SPSDKError: When port configuration cannot be parsed - """ - super().__init__(dev=dev, config=config, timeout=timeout) - - # default configuration taken from parameters (and their default values) - self.i2c_port = port - self.i2c_address = address - self.i2c_speed_khz = speed_khz - - # values can be also overridden by a configuration string - if config: - # config format: i2c[,
,] - cfg = self.get_interface_cfg(config, "i2c").split(",") - try: - self.i2c_address = int(cfg[1], 0) - self.i2c_speed_khz = int(cfg[2], 0) - except IndexError: - pass - except Exception as e: - raise SPSDKError( - "Cannot parse lpcusbsio I2C parameters.\n" - "Expected: i2c[,
,]\n" - f"Given: {config}" - ) from e - - def open(self) -> None: - """Open the interface.""" - if not self.sio.IsOpen(): - self.sio.Open(self.dev_ix) - self.port: LIBUSBSIO.I2C = self.sio.I2C_Open( - clockRate=self.i2c_speed_khz * 1000, portNum=self.i2c_port - ) - if not self.port: - raise SPSDKError("Cannot open lpcusbsio I2C interface.\n") - - def read(self, length: int, timeout: Optional[int] = None) -> bytes: - """Read 'length' amount for bytes from device. - - :param length: Number of bytes to read - :param timeout: Read timeout - :return: Data read from the device - :raises SPSDKConnectionError: When reading data from device fails - :raises SPSDKTimeoutError: When no data received - """ - try: - (data, result) = self.port.DeviceRead( - devAddr=self.i2c_address, rxSize=length - ) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if result < 0 or not data: - raise SPSDKTimeoutError() - logger.debug(f"<{' '.join(f'{b:02x}' for b in data)}>") - return data - - def write(self, data: bytes, timeout: Optional[int] = None) -> None: - """Send data to device. - - :param data: Data to send - :param timeout: Write timeout - :raises SPSDKConnectionError: When sending the data fails - :raises TimeoutError: When data NAKed or could not be written - """ - logger.debug(f"[{' '.join(f'{b:02x}' for b in data)}]") - try: - result = self.port.DeviceWrite(devAddr=self.i2c_address, txData=data) - except Exception as e: - raise SPSDKConnectionError(str(e)) from e - if result < 0: - raise SPSDKTimeoutError() diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/scanner_helper.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/scanner_helper.py deleted file mode 100644 index ede09d3b..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/interfaces/scanner_helper.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2023 NXP -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Helper module used for supporting the scanning.""" - -from dataclasses import dataclass -from typing import Dict, Optional, Tuple - -from ...exceptions import SPSDKKeyError - - -def parse_plugin_config(plugin_conf: str) -> Tuple[str, str]: - """Extract 'identifier' from plugin params and build the params back to original format. - - :param plugin_conf: Plugin configuration string as given on command line - :return: Tuple with identifier and params - """ - params_dict: Dict[str, str] = dict([tuple(p.split("=")) for p in plugin_conf.split(",")]) # type: ignore - if "identifier" not in params_dict: - raise SPSDKKeyError("Plugin parameter must contain 'identifier' key") - identifier = params_dict.pop("identifier") - params = ",".join([f"{key}={value}" for key, value in params_dict.items()]) - return identifier, params - - -@dataclass -class InterfaceParams: - """Interface input parameters.""" - - identifier: str - is_defined: bool - params: Optional[str] = None - extra_params: Optional[str] = None diff --git a/pynitrokey/trussed/bootloader/lpc55_upload/utils/registers.py b/pynitrokey/trussed/bootloader/lpc55_upload/utils/registers.py deleted file mode 100644 index 907a23a2..00000000 --- a/pynitrokey/trussed/bootloader/lpc55_upload/utils/registers.py +++ /dev/null @@ -1,1373 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright 2020-2024 NXP -# -# SPDX-License-Identifier: BSD-3-Clause -"""Module to handle registers descriptions with support for XML files.""" - -import logging -import re -import xml.etree.ElementTree as ET -from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union -from xml.dom import minidom - -from ..exceptions import SPSDKError, SPSDKValueError -from ..utils.exceptions import ( - SPSDKRegsError, - SPSDKRegsErrorBitfieldNotFound, - SPSDKRegsErrorEnumNotFound, - SPSDKRegsErrorRegisterGroupMishmash, - SPSDKRegsErrorRegisterNotFound, -) -from ..utils.images import BinaryImage, BinaryPattern -from ..utils.misc import ( - Endianness, - format_value, - get_bytes_cnt_of_int, - value_to_bool, - value_to_bytes, - value_to_int, - write_file, -) - -HTMLDataElement = Mapping[str, Union[str, dict, list]] -HTMLData = List[HTMLDataElement] - -logger = logging.getLogger(__name__) - - -class RegsEnum: - """Storage for register enumerations.""" - - def __init__( - self, name: str, value: Any, description: str, max_width: int = 0 - ) -> None: - """Constructor of RegsEnum class. Used to store enumeration information of bitfield. - - :param name: Name of enumeration. - :param value: Value of enumeration. - :param description: Text description of enumeration. - :param max_width: Maximal width of enum value used to format output - :raises SPSDKRegsError: Invalid input value. - """ - self.name = name or "N/A" - try: - self.value = value_to_int(value) - except (TypeError, ValueError, SPSDKError) as exc: - raise SPSDKRegsError(f"Invalid Enum Value: {value}") from exc - self.description = description or "N/A" - self.max_width = max_width - - @classmethod - def from_xml_element(cls, xml_element: ET.Element, maxwidth: int = 0) -> "RegsEnum": - """Initialization Enum by XML ET element. - - :param xml_element: Input XML subelement with enumeration data. - :param maxwidth: The maximal width of bitfield for this enum (used for formatting). - :return: The instance of this class. - :raises SPSDKRegsError: Error during enum XML parsing. - """ - name = xml_element.attrib.get("name", "N/A") - if "value" not in xml_element.attrib: - raise SPSDKRegsError(f"Missing Enum Value Key for {name}.") - - raw_val = xml_element.attrib["value"] - try: - value = value_to_int(raw_val) - except (TypeError, ValueError, SPSDKError) as exc: - raise SPSDKRegsError(f"Invalid Enum Value: {raw_val}") from exc - - description = xml_element.attrib.get("description", "N/A").replace( - " ", "\n" - ) - - return cls(name, value, description, maxwidth) - - def get_value_int(self) -> int: - """Method returns Integer value of enum. - - :return: Integer value of Enum. - """ - return self.value - - def get_value_str(self) -> str: - """Method returns formatted value. - - :return: Formatted string with enum value. - """ - return format_value(self.value, self.max_width) - - def add_et_subelement(self, parent: ET.Element) -> None: - """Creates the register XML structure in ElementTree. - - :param parent: The parent object of ElementTree. - """ - element = ET.SubElement(parent, "bit_field_value") - element.set("name", self.name) - element.set("value", self.get_value_str()) - element.set("description", self.description) - - def __str__(self) -> str: - """Overrides 'ToString()' to print register. - - :return: Friendly string with enum information. - """ - output = "" - output += f"Name: {self.name}\n" - output += f"Value: {self.get_value_str()}\n" - output += f"Description: {self.description}\n" - - return output - - -class ConfigProcessor: - """Base class for processing configuration data.""" - - NAME = "NOP" - - def __init__(self, description: str = "") -> None: - """Initialize the processor.""" - self.description = description - - def pre_process(self, value: int) -> int: - """Pre-process value coming from config file.""" - return value - - def post_process(self, value: int) -> int: - """Post-process value going to config file.""" - return value - - def width_update(self, value: int) -> int: - """Update bit-width of value going to config file.""" - return value - - @classmethod - def get_method_name(cls, config_string: str) -> str: - """Return config processor method name.""" - return config_string.split(":")[0] - - @classmethod - def get_params(cls, config_string: str) -> Dict[str, int]: - """Return config processor method parameters.""" - - def split_params(param: str) -> Tuple[str, str]: - """Split key=value pair into a tuple.""" - parts = param.split("=") - if len(parts) != 2: - raise SPSDKRegsError( - f"Invalid param setting: '{param}'. Expected format '='" - ) - return (parts[0], parts[1]) - - parts = config_string.split(";", maxsplit=1)[0].split(":") - if len(parts) == 1: - return {} - params = parts[1].split(",") - params_dict: Dict[str, str] = dict(split_params(p) for p in params) - return {key.lower(): value_to_int(value) for key, value in params_dict.items()} - - @classmethod - def get_description(cls, config_string: str) -> str: - """Return extra description for config processor.""" - parts = config_string.partition(";") - return parts[2].replace("DESC=", "") - - @classmethod - def from_str(cls, config_string: str) -> "ConfigProcessor": - """Create config processor instance from configuration string.""" - return cls(config_string) - - @classmethod - def from_xml(cls, element: ET.Element) -> Optional["ConfigProcessor"]: - """Create config processor from XML data entry.""" - processor_node = element.find("alias[@type='CONFIG_PREPROCESS']") - if processor_node is None: - return None - if "value" not in processor_node.attrib: - raise SPSDKRegsError("CONFIG_PREPROCESS alias node doesn't have a value") - config_string = processor_node.attrib["value"] - method_name = cls.get_method_name(config_string=config_string) - for klass in cls.__subclasses__(): - if klass.NAME == method_name: - return klass.from_str(config_string=config_string) - return None - - -class ShiftRightConfigProcessor(ConfigProcessor): - """Config processor performing the right-shift operation.""" - - NAME = "SHIFT_RIGHT" - - def __init__(self, count: int, description: str = "") -> None: - """Initialize the right-shift config processor. - - :param count: Count of bit for shift operation - :param description: Extra description for config processor, defaults to "" - """ - super().__init__( - description=description - or f"Actual binary value is shifted by {count} bits to right." - ) - self.count = count - - def pre_process(self, value: int) -> int: - """Pre-process value coming from config file.""" - return value >> self.count - - def post_process(self, value: int) -> int: - """Post-process value going to config file.""" - return value << self.count - - def width_update(self, value: int) -> int: - """Update bit-width of value going to config file.""" - return value + self.count - - @classmethod - def from_str(cls, config_string: str) -> "ShiftRightConfigProcessor": - """Create config processor instance from configuration string.""" - name = cls.get_method_name(config_string=config_string) - if name != cls.NAME: - raise SPSDKRegsError(f"Invalid method name '{name}' expected {cls.NAME}") - params = cls.get_params(config_string=config_string) - if "count" not in params: - raise SPSDKRegsError(f"{cls.NAME} requires the COUNT parameter") - description = cls.get_description(config_string=config_string) - return cls(count=value_to_int(params["count"]), description=description) - - -class RegsBitField: - """Storage for register bitfields.""" - - def __init__( - self, - parent: "RegsRegister", - name: str, - offset: int, - width: int, - description: Optional[str] = None, - reset_val: Any = "0", - access: str = "RW", - hidden: bool = False, - config_processor: Optional[ConfigProcessor] = None, - ) -> None: - """Constructor of RegsBitField class. Used to store bitfield information. - - :param parent: Parent register of bitfield. - :param name: Name of bitfield. - :param offset: Bit offset of bitfield. - :param width: Bit width of bitfield. - :param description: Text description of bitfield. - :param reset_val: Reset value of bitfield. - :param access: Access type of bitfield. - :param hidden: The bitfield will be hidden from standard searches. - """ - self.parent = parent - self.name = name or "N/A" - self.offset = offset - self.width = width - self.description = description or "N/A" - self.reset_value = value_to_int(reset_val, 0) - self.access = access - self.hidden = hidden - self._enums: List[RegsEnum] = [] - self.config_processor = config_processor or ConfigProcessor() - self.config_width = self.config_processor.width_update(width) - self.set_value(self.reset_value, raw=True) - - @classmethod - def from_xml_element( - cls, xml_element: ET.Element, parent: "RegsRegister" - ) -> "RegsBitField": - """Initialization register by XML ET element. - - :param xml_element: Input XML subelement with register data. - :param parent: Reference to parent RegsRegister object. - :return: The instance of this class. - """ - name = xml_element.attrib.get("name", "N/A") - offset = value_to_int(xml_element.attrib.get("offset", 0)) - width = value_to_int(xml_element.attrib.get("width", 0)) - description = xml_element.attrib.get("description", "N/A").replace( - " ", "\n" - ) - access = xml_element.attrib.get("access", "R/W") - reset_value = value_to_int(xml_element.attrib.get("reset_value", 0)) - hidden = xml_element.tag != "bit_field" - config_processor = ConfigProcessor.from_xml(xml_element) - - bitfield = cls( - parent, - name, - offset, - width, - description, - reset_value, - access, - hidden, - config_processor, - ) - - for xml_enum in xml_element.findall("bit_field_value"): - bitfield.add_enum(RegsEnum.from_xml_element(xml_enum, width)) - - return bitfield - - def has_enums(self) -> bool: - """Returns if the bitfields has enums. - - :return: True is has enums, False otherwise. - """ - return len(self._enums) > 0 - - def get_enums(self) -> List[RegsEnum]: - """Returns bitfield enums. - - :return: List of bitfield enumeration values. - """ - return self._enums - - def add_enum(self, enum: RegsEnum) -> None: - """Add bitfield enum. - - :param enum: New enumeration value for bitfield. - """ - self._enums.append(enum) - - def get_value(self) -> int: - """Returns integer value of the bitfield. - - :return: Current value of bitfield. - """ - reg_val = self.parent.get_value(raw=False) - value = reg_val >> self.offset - mask = (1 << self.width) - 1 - value = value & mask - value = self.config_processor.post_process(value) - return value - - def get_reset_value(self) -> int: - """Returns integer reset value of the bitfield. - - :return: Reset value of bitfield. - """ - return self.reset_value - - def set_value(self, new_val: Any, raw: bool = False) -> None: - """Updates the value of the bitfield. - - :param new_val: New value of bitfield. - :param raw: If set, no automatic modification of value is applied. - :raises SPSDKValueError: The input value is out of range. - """ - new_val_int = value_to_int(new_val) - new_val_int = self.config_processor.pre_process(new_val_int) - if new_val_int > 1 << self.width: - raise SPSDKValueError("The input value is out of bitfield range") - reg_val = self.parent.get_value(raw=raw) - - mask = ((1 << self.width) - 1) << self.offset - reg_val = reg_val & ~mask - value = (new_val_int << self.offset) & mask - reg_val = reg_val | value - self.parent.set_value(reg_val, raw) - - def set_enum_value(self, new_val: str, raw: bool = False) -> None: - """Updates the value of the bitfield by its enum value. - - :param new_val: New enum value of bitfield. - :param raw: If set, no automatic modification of value is applied. - :raises SPSDKRegsErrorEnumNotFound: Input value cannot be decoded. - """ - try: - val_int = self.get_enum_constant(new_val) - except SPSDKRegsErrorEnumNotFound: - # Try to decode standard input - try: - val_int = value_to_int(new_val) - except TypeError: - raise SPSDKRegsErrorEnumNotFound # pylint: disable=raise-missing-from - self.set_value(val_int, raw) - - def get_enum_value(self) -> Union[str, int]: - """Returns enum value of the bitfield. - - :return: Current value of bitfield. - """ - value = self.get_value() - for enum in self._enums: - if enum.get_value_int() == value: - return enum.name - # return value - return self.get_hex_value() - - def get_hex_value(self) -> str: - """Get the value of register in string hex format. - - :return: Hexadecimal value of register. - """ - fmt = f"0{self.config_width // 4}X" - val = f"0x{format(self.get_value(), fmt)}" - return val - - def get_enum_constant(self, enum_name: str) -> int: - """Returns constant representation of enum by its name. - - :return: Constant of enum. - :raises SPSDKRegsErrorEnumNotFound: The enum has not been found. - """ - for enum in self._enums: - if enum.name == enum_name: - return enum.get_value_int() - - raise SPSDKRegsErrorEnumNotFound( - f"The enum for {enum_name} has not been found." - ) - - def get_enum_names(self) -> List[str]: - """Returns list of the enum strings. - - :return: List of enum names. - """ - return [x.name for x in self._enums] - - def add_et_subelement(self, parent: ET.Element) -> None: - """Creates the register XML structure in ElementTree. - - :param parent: The parent object of ElementTree. - """ - element = ET.SubElement( - parent, "reserved_bit_field" if self.hidden else "bit_field" - ) - element.set("offset", hex(self.offset)) - element.set("width", str(self.width)) - element.set("name", self.name) - element.set("access", self.access) - element.set("reset_value", format_value(self.reset_value, self.width)) - element.set("description", self.description) - for enum in self._enums: - enum.add_et_subelement(element) - - def __str__(self) -> str: - """Override 'ToString()' to print register. - - :return: Friendly looking string that describes the bitfield. - """ - output = "" - output += f"Name: {self.name}\n" - output += f"Offset: {self.offset} bits\n" - output += f"Width: {self.width} bits\n" - output += f"Access: {self.access} bits\n" - output += f"Reset val:{self.reset_value}\n" - output += f"Description: \n {self.description}\n" - if self.hidden: - output += "This is hidden bitfield!\n" - - i = 0 - for enum in self._enums: - output += f"Enum #{i}: \n" + str(enum) - i += 1 - - return output - - -class RegsRegister: - """Initialization register by input information.""" - - def __init__( - self, - name: str, - offset: int, - width: int, - description: Optional[str] = None, - reverse: bool = False, - access: Optional[str] = None, - config_as_hexstring: bool = False, - otp_index: Optional[int] = None, - reverse_subregs_order: bool = False, - base_endianness: Endianness = Endianness.BIG, - alt_widths: Optional[List[int]] = None, - ) -> None: - """Constructor of RegsRegister class. Used to store register information. - - :param name: Name of register. - :param offset: Byte offset of register. - :param width: Bit width of register. - :param description: Text description of register. - :param reverse: Multi byte register value could be printed in reverse order. - :param access: Access type of register. - :param config_as_hexstring: Config is stored as a hex string. - :param otp_index: Index of OTP fuse. - :param reverse_subregs_order: Reverse order of sub registers. - :param base_endianness: Base endianness for bytes import/export of value. - :param alt_widths: List of alternative widths. - """ - if width % 8 != 0: - raise SPSDKValueError( - "SPSDK Register supports only widths in multiply 8 bits." - ) - self.name = name - self.offset = offset - self.width = width - self.description = description or "N/A" - self.access = access or "RW" - self.reverse = reverse - self._bitfields: List[RegsBitField] = [] - self._set_value_hooks: List = [] - self._value = 0 - self._reset_value = 0 - self.config_as_hexstring = config_as_hexstring - self.otp_index = otp_index - self.reverse_subregs_order = reverse_subregs_order - self.base_endianness = base_endianness - self.alt_widths = alt_widths - self._alias_names: List[str] = [] - - # Grouped register members - self.sub_regs: List["RegsRegister"] = [] - self._sub_regs_width_init = False - self._sub_regs_width = 0 - - def __eq__(self, obj: Any) -> bool: - """Compare if the objects has same settings.""" - if not isinstance(obj, self.__class__): - return False - if obj.name != self.name: - return False - if obj.width != self.width: - return False - if obj.reverse != self.reverse: - return False - if obj._value != self._value: - return False - if obj._reset_value != self._reset_value: - return False - return True - - @classmethod - def from_xml_element(cls, xml_element: ET.Element) -> "RegsRegister": - """Initialization register by XML ET element. - - :param xml_element: Input XML subelement with register data. - :return: The instance of this class. - """ - name = xml_element.attrib.get("name", "N/A") - offset = value_to_int(xml_element.attrib.get("offset", 0)) - width = value_to_int(xml_element.attrib.get("width", 0)) - description = xml_element.attrib.get("description", "N/A").replace( - " ", "\n" - ) - reverse = (xml_element.attrib.get("reversed", "False")) == "True" - access = xml_element.attrib.get("access", "N/A") - otp_index_raw = xml_element.attrib.get("otp_index") - otp_index = None - if otp_index_raw: - otp_index = value_to_int(otp_index_raw) - reg = cls( - name, - offset, - width, - description, - reverse, - access, - otp_index=otp_index, - ) - value = xml_element.attrib.get("value") - if value: - reg.set_value(value) - - if xml_element.text: - xml_bitfields = xml_element.findall("bit_field") - xml_bitfields.extend(xml_element.findall("reserved_bit_field")) - xml_bitfields_len = len(xml_bitfields) - for xml_bitfield in xml_bitfields: - bitfield = RegsBitField.from_xml_element(xml_bitfield, reg) - if ( - xml_bitfields_len == 1 - and bitfield.width == reg.width - and not bitfield.has_enums() - ): - if len(reg.description) < len(bitfield.description): - reg.description = bitfield.description - reg.access = bitfield.access - reg._reset_value = bitfield.reset_value - else: - if reg.access == "N/A": - reg.access = "Bitfields depended" - reg.add_bitfield(bitfield) - return reg - - def add_alias(self, alias: str) -> None: - """Add alias name to register. - - :param alias: Register name alias. - """ - if not alias in self._alias_names: - self._alias_names.append(alias) - - def has_group_registers(self) -> bool: - """Returns true if register is compounded from sub-registers. - - :return: True if register has sub-registers, False otherwise. - """ - return len(self.sub_regs) > 0 - - def add_group_reg(self, reg: "RegsRegister") -> None: - """Add group element for this register. - - :param reg: Register member of this register group. - :raises SPSDKRegsErrorRegisterGroupMishmash: When any inconsistency is detected. - """ - first_member = not self.has_group_registers() - if first_member: - if self.offset == 0: - self.offset = reg.offset - if self.width == 0: - self.width = reg.width - else: - self._sub_regs_width_init = True - self._sub_regs_width = reg.width - if self.access == "RW": - self.access = reg.access - else: - # There is strong rule that supported group MUST be in one row in memory! - if not self._sub_regs_width_init: - if self.offset + self.width // 8 != reg.offset: - raise SPSDKRegsErrorRegisterGroupMishmash( - f"The register {reg.name} doesn't follow the previous one." - ) - self.width += reg.width - else: - if self.offset + self.width // 8 <= reg.offset: - raise SPSDKRegsErrorRegisterGroupMishmash( - f"The register {reg.name} doesn't follow the previous one." - ) - self._sub_regs_width += reg.width - if self._sub_regs_width > self.width: - raise SPSDKRegsErrorRegisterGroupMishmash( - f"The register {reg.name} bigger width than is defined." - ) - if self.sub_regs[0].width != reg.width: - raise SPSDKRegsErrorRegisterGroupMishmash( - f"The register {reg.name} has different width." - ) - if self.access != reg.access: - raise SPSDKRegsErrorRegisterGroupMishmash( - f"The register {reg.name} has different access type." - ) - reg.base_endianness = self.base_endianness - self.sub_regs.append(reg) - - def add_et_subelement(self, parent: ET.Element) -> None: - """Creates the register XML structure in ElementTree. - - :param parent: The parent object of ElementTree. - """ - element = ET.SubElement(parent, "register") - element.set("offset", hex(self.offset)) - element.set("width", str(self.width)) - element.set("name", self.name) - element.set("reversed", str(self.reverse)) - element.set("description", self.description) - if self.otp_index: - element.set("otp_index", str(self.otp_index)) - for bitfield in self._bitfields: - bitfield.add_et_subelement(element) - - def set_value(self, val: Any, raw: bool = False) -> None: - """Set the new value of register. - - :param val: The new value to set. - :param raw: Do not use any modification hooks. - :raises SPSDKError: When invalid values is loaded into register - """ - try: - if isinstance(val, (bytes, bytearray)): - value = int.from_bytes(val, self.base_endianness.value) - else: - value = value_to_int(val) - if value >= 1 << self.width: - raise SPSDKError( - f"Input value {value} doesn't fit into register of width {self.width}." - ) - - alt_width = self.get_alt_width(value) - - if not raw: - for hook in self._set_value_hooks: - value = hook[0](value, hook[1]) - if self.reverse: - # The value_to_int internally is using BIG endian - val_bytes = value_to_bytes( - value, - align_to_2n=False, - byte_cnt=alt_width // 8, - endianness=Endianness.BIG, - ) - value = value.from_bytes(val_bytes, Endianness.LITTLE.value) - - if self.has_group_registers(): - # Update also values in sub registers - subreg_width = self.sub_regs[0].width - sub_regs = self.sub_regs[: alt_width // subreg_width] - for index, sub_reg in enumerate(sub_regs, start=1): - if self.reverse_subregs_order: - bit_pos = alt_width - index * subreg_width - else: - bit_pos = (index - 1) * subreg_width - - sub_reg.set_value( - (value >> bit_pos) & ((1 << subreg_width) - 1), raw=raw - ) - else: - self._value = value - - except SPSDKError as exc: - raise SPSDKError(f"Loaded invalid value {str(val)}") from exc - - def reset_value(self, raw: bool = False) -> None: - """Reset the value of register. - - :param raw: Do not use any modification hooks. - """ - self.set_value(self.get_reset_value(), raw) - - def get_alt_width(self, value: int) -> int: - """Get alternative width of register. - - :param value: Input value to recognize width - :return: Current width - """ - alt_width = self.width - if self.alt_widths: - real_byte_cnt = get_bytes_cnt_of_int(value, align_to_2n=False) - self.alt_widths.sort() - for alt in self.alt_widths: - if real_byte_cnt <= alt // 8: - alt_width = alt - break - return alt_width - - def get_value(self, raw: bool = False) -> int: - """Get the value of register. - - :param raw: Do not use any modification hooks. - """ - if self.has_group_registers(): - # Update local value, by the sub register values - subreg_width = self.sub_regs[0].width - sub_regs_value = 0 - for index, sub_reg in enumerate(self.sub_regs, start=1): - if self.reverse_subregs_order: - bit_pos = self.width - index * subreg_width - else: - bit_pos = (index - 1) * subreg_width - sub_regs_value |= sub_reg.get_value(raw=raw) << (bit_pos) - value = sub_regs_value - else: - value = self._value - - alt_width = self.get_alt_width(value) - - if not raw and self.reverse: - val_bytes = value_to_bytes( - value, - align_to_2n=False, - byte_cnt=alt_width // 8, - endianness=self.base_endianness, - ) - value = value.from_bytes( - val_bytes, - Endianness.BIG.value - if self.base_endianness == Endianness.LITTLE - else Endianness.LITTLE.value, - ) - - return value - - def get_bytes_value(self, raw: bool = False) -> bytes: - """Get the bytes value of register. - - :param raw: Do not use any modification hooks. - :return: Register value in bytes. - """ - value = self.get_value(raw=raw) - return value_to_bytes( - value, - align_to_2n=False, - byte_cnt=self.get_alt_width(value) // 8, - endianness=self.base_endianness, - ) - - def get_hex_value(self, raw: bool = False) -> str: - """Get the value of register in string hex format. - - :param raw: Do not use any modification hooks. - :return: Hexadecimal value of register. - """ - val_int = self.get_value(raw=raw) - count = "0" + str(self.get_alt_width(val_int) // 4) - value = f"{val_int:{count}X}" - if not self.config_as_hexstring: - value = "0x" + value - return value - - def get_reset_value(self) -> int: - """Returns reset value of the register. - - :return: Reset value of register. - """ - value = self._reset_value - for bitfield in self._bitfields: - width = bitfield.width - offset = bitfield.offset - val = bitfield.reset_value - value |= (val & ((1 << width) - 1)) << offset - - return value - - def add_bitfield(self, bitfield: RegsBitField) -> None: - """Add register bitfield. - - :param bitfield: New bitfield value for register. - """ - self._bitfields.append(bitfield) - - def get_bitfields(self, exclude: Optional[List[str]] = None) -> List[RegsBitField]: - """Returns register bitfields. - - Method allows exclude some bitfields by their names. - :param exclude: Exclude list of bitfield names if needed. - :return: Returns List of register bitfields. - """ - ret = [] - for bitf in self._bitfields: - if bitf.hidden: - continue - if exclude and bitf.name.startswith(tuple(exclude)): - continue - ret.append(bitf) - return ret - - def get_bitfield_names(self, exclude: Optional[List[str]] = None) -> List[str]: - """Returns list of the bitfield names. - - :param exclude: Exclude list of bitfield names if needed. - :return: List of bitfield names. - """ - return [x.name for x in self.get_bitfields(exclude)] - - def find_bitfield(self, name: str) -> RegsBitField: - """Returns the instance of the bitfield by its name. - - :param name: The name of the bitfield. - :return: Instance of the bitfield. - :raises SPSDKRegsErrorBitfieldNotFound: The bitfield doesn't exist. - """ - for bitfield in self._bitfields: - if name == bitfield.name: - return bitfield - - raise SPSDKRegsErrorBitfieldNotFound( - f" The {name} is not found in register {self.name}." - ) - - def add_setvalue_hook(self, hook: Callable, context: Optional[Any] = None) -> None: - """Set the value hook for write operation. - - :param hook: Callable hook for set value operation. - :param context: Context data for this hook. - """ - self._set_value_hooks.append((hook, context)) - - def __str__(self) -> str: - """Override 'ToString()' to print register. - - :return: Friendly looking string that describes the register. - """ - output = "" - output += f"Name: {self.name}\n" - output += f"Offset: 0x{self.offset:04X}\n" - output += f"Width: {self.width} bits\n" - output += f"Access: {self.access}\n" - output += f"Description: \n {self.description}\n" - if self.otp_index: - output += f"OTP Word: \n {self.otp_index}\n" - - i = 0 - for bitfield in self._bitfields: - output += f"Bitfield #{i}: \n" + str(bitfield) - i += 1 - - return output - - -class Registers: - """SPSDK Class for registers handling.""" - - TEMPLATE_NOTE = ( - "All registers is possible to define also as one value although the bitfields are used. " - "Instead of bitfields: ... field, the value: ... definition works as well." - ) - - def __init__( - self, device_name: str, base_endianness: Endianness = Endianness.BIG - ) -> None: - """Initialization of Registers class.""" - self._registers: List[RegsRegister] = [] - self.dev_name = device_name - self.base_endianness = base_endianness - - def __eq__(self, obj: Any) -> bool: - """Compare if the objects has same settings.""" - if not ( - isinstance(obj, self.__class__) - and obj.dev_name == self.dev_name - and obj.base_endianness == self.base_endianness - ): - return False - ret = obj._registers == self._registers - return ret - - def find_reg(self, name: str, include_group_regs: bool = False) -> RegsRegister: - """Returns the instance of the register by its name. - - :param name: The name of the register. - :param include_group_regs: The algorithm will check also group registers. - :return: Instance of the register. - :raises SPSDKRegsErrorRegisterNotFound: The register doesn't exist. - """ - for reg in self._registers: - if name == reg.name: - return reg - if name in reg._alias_names: - return reg - if include_group_regs and reg.has_group_registers(): - for sub_reg in reg.sub_regs: - if name == sub_reg.name: - return sub_reg - - raise SPSDKRegsErrorRegisterNotFound( - f"The {name} is not found in loaded registers for {self.dev_name} device." - ) - - def add_register(self, reg: RegsRegister) -> None: - """Adds register into register list. - - :param reg: Register to add to the class. - :raises SPSDKError: Invalid type has been provided. - :raises SPSDKRegsError: Cannot add register with same name - """ - if not isinstance(reg, RegsRegister): - raise SPSDKError("The 'reg' has invalid type.") - - if reg.name in self.get_reg_names(): - raise SPSDKRegsError(f"Cannot add register with same name: {reg.name}.") - - for idx, register in enumerate(self._registers): - # TODO solve problem with group register that are always at 0 offset - if register.offset == reg.offset != 0: - logger.debug( - f"Found register at the same offset {hex(reg.offset)}" - f", adding {reg.name} as an alias to {register.name}" - ) - self._registers[idx].add_alias(reg.name) - self._registers[idx]._bitfields.extend(reg._bitfields) - return - # update base endianness for all registers in group - reg.base_endianness = self.base_endianness - self._registers.append(reg) - - def remove_registers(self) -> None: - """Remove all registers.""" - self._registers.clear() - - def get_registers( - self, exclude: Optional[List[str]] = None, include_group_regs: bool = False - ) -> List[RegsRegister]: - """Returns list of the registers. - - Method allows exclude some register by their names. - :param exclude: Exclude list of register names if needed. - :param include_group_regs: The algorithm will check also group registers. - :return: List of register names. - """ - if exclude: - regs = [r for r in self._registers if not r.name.startswith(tuple(exclude))] - else: - regs = self._registers.copy() - if include_group_regs: - sub_regs = [] - for reg in regs: - if reg.has_group_registers(): - sub_regs.extend(reg.sub_regs) - regs.extend(sub_regs) - - return regs - - def get_reg_names( - self, exclude: Optional[List[str]] = None, include_group_regs: bool = False - ) -> List[str]: - """Returns list of the register names. - - :param exclude: Exclude list of register names if needed. - :param include_group_regs: The algorithm will check also group registers. - :return: List of register names. - """ - return [x.name for x in self.get_registers(exclude, include_group_regs)] - - def reset_values(self, exclude: Optional[List[str]] = None) -> None: - """The method reset values in registers. - - :param exclude: The list of register names to be excluded. - """ - for reg in self.get_registers(exclude): - reg.reset_value(True) - - def __str__(self) -> str: - """Override 'ToString()' to print register. - - :return: Friendly looking string that describes the registers. - """ - output = "" - output += "Device name: " + self.dev_name + "\n" - for reg in self._registers: - output += str(reg) + "\n" - - return output - - def write_xml(self, file_name: str) -> None: - """Write loaded register structures into XML file. - - :param file_name: The name of XML file that should be created. - """ - xml_root = ET.Element("regs") - for reg in self._registers: - reg.add_et_subelement(xml_root) - - no_pretty_data = minidom.parseString( - ET.tostring(xml_root, encoding="unicode", short_empty_elements=False) - ) - write_file(no_pretty_data.toprettyxml(), file_name, encoding="utf-8") - - def image_info( - self, size: int = 0, pattern: BinaryPattern = BinaryPattern("zeros") - ) -> BinaryImage: - """Export Registers into binary information. - - :param size: Result size of Image, 0 means automatic minimal size. - :param pattern: Pattern of gaps, defaults to "zeros" - """ - image = BinaryImage(self.dev_name, size=size, pattern=pattern) - for reg in self._registers: - description = reg.description - if reg._alias_names: - description += f"\n Alias names: {', '.join(reg._alias_names)}" - image.add_image( - BinaryImage( - reg.name, - reg.width // 8, - offset=reg.offset, - description=description, - binary=reg.get_bytes_value(raw=True), - ) - ) - - return image - - def export( - self, size: int = 0, pattern: BinaryPattern = BinaryPattern("zeros") - ) -> bytes: - """Export Registers into binary. - - :param size: Result size of Image, 0 means automatic minimal size. - :param pattern: Pattern of gaps, defaults to "zeros" - """ - return self.image_info(size, pattern).export() - - def parse(self, binary: bytes) -> None: - """Parse the binary data values into loaded registers. - - :param binary: Binary data to parse. - """ - bin_len = len(binary) - if bin_len < len(self.image_info()): - logger.info( - f"Input binary is smaller than registers supports: {bin_len} != {len(self.image_info())}" - ) - for reg in self.get_registers(): - if bin_len < reg.offset + reg.width // 8: - logger.debug(f"Parsing of binary block ends at {reg.name}") - break - reg.set_value(binary[reg.offset : reg.offset + reg.width // 8], raw=True) - - def _get_bitfield_yaml_description(self, bitfield: RegsBitField) -> str: - """Create the valuable comment for bitfield. - - :param bitfield: Bitfield used to generate description. - :return: Bitfield description. - """ - description = f"Offset: {bitfield.offset}b, Width: {bitfield.config_width}b" - if bitfield.description not in ("", "."): - description += ", " + bitfield.description.replace(" ", "\n") - if bitfield.config_processor.description: - description += ".\n NOTE: " + bitfield.config_processor.description - if bitfield.has_enums(): - for enum in bitfield.get_enums(): - descr = enum.description if enum.description != "." else enum.name - enum_description = descr.replace(" ", "\n") - description += ( - f"\n- {enum.name}, ({enum.get_value_int()}): {enum_description}" - ) - return description - - def get_validation_schema(self) -> Dict: - """Get the JSON SCHEMA for registers. - - :return: JSON SCHEMA. - """ - properties: Dict[str, Any] = {} - for reg in self.get_registers(): - bitfields = reg.get_bitfields() - reg_schema = [ - { - "type": ["string", "number"], - "skip_in_template": len(bitfields) > 0, - # "format": "number", # TODO add option to hexstring - "template_value": f"{reg.get_hex_value()}", - }, - { # Obsolete type - "type": "object", - "required": ["value"], - "skip_in_template": True, - "additionalProperties": False, - "properties": { - "value": { - "type": ["string", "number"], - # "format": "number", # TODO add option to hexstring - "template_value": f"{reg.get_hex_value()}", - } - }, - }, - ] - - if bitfields: - bitfields_schema = {} - for bitfield in bitfields: - if not bitfield.has_enums(): - bitfields_schema[bitfield.name] = { - "type": ["string", "number"], - "title": f"{bitfield.name}", - "description": self._get_bitfield_yaml_description( - bitfield - ), - "template_value": bitfield.get_value(), - } - else: - bitfields_schema[bitfield.name] = { - "type": ["string", "number"], - "title": f"{bitfield.name}", - "description": self._get_bitfield_yaml_description( - bitfield - ), - "enum_template": bitfield.get_enum_names(), - "minimum": 0, - "maximum": (1 << bitfield.width) - 1, - "template_value": bitfield.get_enum_value(), - } - # Extend register schema by obsolete style - reg_schema.append( - { - "type": "object", - "required": ["bitfields"], - "skip_in_template": True, - "additionalProperties": False, - "properties": { - "bitfields": { - "type": "object", - "properties": bitfields_schema, - } - }, - } - ) - # Extend by new style of bitfields - reg_schema.append( - { - "type": "object", - "skip_in_template": False, - "required": [], - "additionalProperties": False, - "properties": bitfields_schema, - }, - ) - - properties[reg.name] = { - "title": f"{reg.name}", - "description": f"{reg.description}", - "oneOf": reg_schema, - } - - return {"type": "object", "title": self.dev_name, "properties": properties} - - # pylint: disable=no-self-use #It's better to have this function visually close to callies - def _filter_by_names( - self, items: List[ET.Element], names: List[str] - ) -> List[ET.Element]: - """Filter out all items in the "items" tree,whose name starts with one of the strings in "names" list. - - :param items: Items to be filtered out. - :param names: Names to filter out. - :return: Filtered item elements list. - """ - return [ - item for item in items if not item.attrib["name"].startswith(tuple(names)) - ] - - # pylint: disable=dangerous-default-value - def load_registers_from_xml( - self, - xml: str, - filter_reg: Optional[List[str]] = None, - grouped_regs: Optional[List[dict]] = None, - ) -> None: - """Function loads the registers from the given XML. - - :param xml: Input XML data in string format. - :param filter_reg: List of register names that should be filtered out. - :param grouped_regs: List of register prefixes names to be grouped into one. - :raises SPSDKRegsError: XML parse problem occurs. - """ - - def is_reg_in_group(reg: str) -> Union[dict, None]: - """Help function to recognize if the register should be part of group.""" - if grouped_regs: - for group in grouped_regs: - # pylint: disable=anomalous-backslash-in-string # \d is a part of the regex pattern - if re.fullmatch(f"{group['name']}" + r"\d+", reg) is not None: - return group - return None - - try: - xml_elements = ET.parse(xml) - except ET.ParseError as exc: - raise SPSDKRegsError(f"Cannot Parse XML data: {str(exc)}") from exc - xml_registers = xml_elements.findall("register") - xml_registers = self._filter_by_names(xml_registers, filter_reg or []) - # Load all registers into the class - for xml_reg in xml_registers: - group = is_reg_in_group(xml_reg.attrib["name"]) - if group: - try: - group_reg = self.find_reg(group["name"]) - except SPSDKRegsErrorRegisterNotFound: - group_reg = RegsRegister( - name=group["name"], - offset=value_to_int(group.get("offset", 0)), - width=value_to_int(group.get("width", 0)), - description=group.get( - "description", f"Group of {group['name']} registers." - ), - reverse=value_to_bool(group.get("reversed", False)), - access=group.get("access", None), - config_as_hexstring=group.get("config_as_hexstring", False), - reverse_subregs_order=group.get("reverse_subregs_order", False), - alt_widths=group.get("alternative_widths"), - ) - - self.add_register(group_reg) - group_reg.add_group_reg(RegsRegister.from_xml_element(xml_reg)) - else: - self.add_register(RegsRegister.from_xml_element(xml_reg)) - - def load_yml_config(self, yml_data: Dict[str, Any]) -> None: - """The function loads the configuration from YML file. - - :param yml_data: The YAML commented data with register values. - """ - for reg_name in yml_data.keys(): - reg_value = yml_data[reg_name] - register = self.find_reg(reg_name, include_group_regs=True) - if isinstance(reg_value, dict): - if "value" in reg_value.keys(): - raw_val = reg_value["value"] - val = ( - int(raw_val, 16) - if register.config_as_hexstring and isinstance(raw_val, str) - else value_to_int(raw_val) - ) - register.set_value(val, False) - else: - bitfields = ( - reg_value["bitfields"] - if "bitfields" in reg_value.keys() - else reg_value - ) - for bitfield_name in bitfields: - bitfield_val = bitfields[bitfield_name] - try: - bitfield = register.find_bitfield(bitfield_name) - except SPSDKRegsErrorBitfieldNotFound: - logger.error( - f"The {bitfield_name} is not found in register {register.name}." - ) - continue - try: - bitfield.set_enum_value(bitfield_val, True) - except SPSDKValueError as e: - raise SPSDKError( - f"Bitfield value: {hex(bitfield_val)} of {bitfield.name} is out of range." - + f"\nBitfield width is {bitfield.width} bits" - ) from e - except SPSDKError: - # New versions of register data do not contain register and bitfield value in enum - old_bitfield = bitfield_val - bitfield_val = bitfield_val.replace( - bitfield.name + "_", "" - ).replace(register.name + "_", "") - # Some bitfield were renamed from ENABLE to ALLOW - bitfield_val = ( - "ALLOW" if bitfield_val == "ENABLE" else bitfield_val - ) - logger.warning( - f"Bitfield {old_bitfield} not found, trying backward" - " compatibility mode with {bitfield_val}" - ) - bitfield.set_enum_value(bitfield_val, True) - - # Run the processing of loaded register value - register.set_value(register.get_value(True), False) - elif isinstance(reg_value, (int, str)): - val = ( - int(reg_value, 16) - if register.config_as_hexstring and isinstance(reg_value, str) - else value_to_int(reg_value) - ) - register.set_value(val, False) - - else: - logger.error(f"There are no data for {reg_name} register.") - - logger.debug(f"The register {reg_name} has been loaded from configuration.") - - def get_config(self, diff: bool = False) -> Dict[str, Any]: - """Get the whole configuration in dictionary. - - :param diff: Get only configuration with difference value to reset state. - :return: Dictionary of registers values. - """ - ret: Dict[str, Any] = {} - for reg in self.get_registers(): - if diff and reg.get_value(raw=True) == reg.get_reset_value(): - continue - bitfields = reg.get_bitfields() - if bitfields: - btf = {} - for bitfield in bitfields: - if diff and bitfield.get_value() == bitfield.get_reset_value(): - continue - btf[bitfield.name] = bitfield.get_enum_value() - ret[reg.name] = btf - else: - ret[reg.name] = reg.get_hex_value() - - return ret