#!/usr/bin/env python3
"""Test the configuration checks generated by generate_config_checks.py.
"""

## Copyright The Mbed TLS Contributors
## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later

import unittest

import scripts_path # pylint: disable=unused-import
from mbedtls_framework import unittest_config_checks


class CryptoTestConfigChecks(unittest_config_checks.TestConfigChecks):
    """TF-PSA-Crypto unit tests for config checks."""

    #pylint: disable=invalid-name # uppercase letters make sense here

    PROJECT_CONFIG_C = 'core/tf_psa_crypto_config.c'
    PROJECT_SPECIFIC_INCLUDE_DIRECTORIES = [
        'drivers/builtin/include',
    ]

    def test_crypto_config_read(self) -> None:
        """Check that crypto_config.h is read in crypto."""
        self.bad_case('#error witness',
                      error='witness')

    def test_crypto_mbedtls_config_not_read(self) -> None:
        """Check that mbedtls_config.h is not read in crypto."""
        self.good_case('',
                       '#error "mbedtls_config.h was read"')

    def test_crypto_undef_MBEDTLS_FS_IO(self) -> None:
        """A sample error expected from check_config.h."""
        self.bad_case('#undef MBEDTLS_FS_IO',
                      error=('MBEDTLS_PSA_ITS_FILE_C'))

    def test_crypto_non_exempt_undef_MBEDTLS_FS_IO(self) -> None:
        """CONFIG_CHECK_BYPASS does not affect check_config.h.

        This is the historical behavior, which may change if needed.
        """
        self.bad_case('#undef MBEDTLS_FS_IO',
                      extra_options=['-DTF_PSA_CRYPTO_CONFIG_CHECK_BYPASS'])

    def test_crypto_define_MBEDTLS_MD5_C(self) -> None:
        """Error when setting an internal option that used to be public."""
        self.bad_case('#define MBEDTLS_MD5_C',
                      error=('MBEDTLS_MD5_C'))

    def test_crypto_exempt_define_MBEDTLS_MD5_C(self) -> None:
        """Bypassed error when setting an internal option that used to be public."""
        self.good_case('#define MBEDTLS_MD5_C',
                       extra_options=['-DTF_PSA_CRYPTO_CONFIG_CHECK_BYPASS'])

    @unittest.skip("Currently we can't check if a macro gets undefined.")
    def test_crypto_undef_MBEDTLS_MD5_C(self) -> None:
        """Error when unsetting an internal option that used to be public."""
        self.bad_case('#undef MBEDTLS_MD5_C',
                      error=('MBEDTLS_MD5_C'))

    def test_crypto_define_MBEDTLS_PADLOCK_C(self) -> None:
        """Error when setting a removed option."""
        self.bad_case('#define MBEDTLS_PADLOCK_C',
                      error=('MBEDTLS_PADLOCK_C was removed'))

    def test_crypto_define_MBEDTLS_PSA_CRYPTO_CONFIG(self) -> None:
        """It's ok to set MBEDTLS_PSA_CRYPTO_CONFIG (now effectively always on)."""
        self.good_case('#define MBEDTLS_PSA_CRYPTO_CONFIG')

    def test_crypto_define_MBEDTLS_USE_PSA_CRYPTO(self) -> None:
        """It's ok to set MBEDTLS_USE_PSA_CRYPTO (now effectively always on)."""
        self.good_case('#define MBEDTLS_USE_PSA_CRYPTO')


class CryptoTestRNGConfigChecks(unittest_config_checks.TestConfigChecks):
    """TF-PSA-Crypto unit tests for random generator config checks."""

    PROJECT_CONFIG_C = 'core/tf_psa_crypto_config.c'
    PROJECT_SPECIFIC_INCLUDE_DIRECTORIES = [
        'drivers/builtin/include',
    ]

    def test_rng_no_drbg(self) -> None:
        """Check that a mostly complete library can't be built without a DRBG."""
        self.bad_case('''
                      #undef MBEDTLS_CTR_DRBG_C
                      #undef MBEDTLS_HMAC_DRBG_C
                      #undef PSA_WANT_ALG_DETERMINISTIC_ECDSA // would reenable HMAC_DRBG
                      ''',
                      error=r'No DRBG module')

    def test_rng_ctr_drbg_only(self) -> None:
        """Check that CTR_DRBG suffices as a DRBG."""
        self.good_case('''
                       #define MBEDTLS_CTR_DRBG_C
                       #undef MBEDTLS_HMAC_DRBG_C
                       #undef PSA_WANT_ALG_DETERMINISTIC_ECDSA // would reenable HMAC_DRBG
                       ''')

    def test_rng_hmac_drbg_only(self) -> None:
        """Check that HMAC_DRBG suffices as a DRBG."""
        self.good_case('''
                       #undef MBEDTLS_CTR_DRBG_C
                       #define MBEDTLS_HMAC_DRBG_C
                       ''')

    def test_rng_strength_256_ok(self) -> None:
        """Check that the default config meets a 256-bit RNG strength."""
        self.good_case('''
                       #undef MBEDTLS_PSA_CRYPTO_RNG_STRENGTH
                       #define MBEDTLS_PSA_CRYPTO_RNG_STRENGTH 256
                       ''')

    def test_rng_strength_1024_bad(self) -> None:
        """Check that a 1024-bit RNG strength is rejected.

        This is more than we can reasonably foresee at this time
        (you would need a 1024-bit hash).
        """
        self.bad_case('''
                      #undef MBEDTLS_PSA_CRYPTO_RNG_STRENGTH
                      #define MBEDTLS_PSA_CRYPTO_RNG_STRENGTH 1024
                      ''',
                      error=r'hash size.*MBEDTLS_PSA_CRYPTO_RNG_STRENGTH')

    def test_rng_strength_256_aes_128(self) -> None:
        """Check that AES-128 is flagged as not achieving 256-bit RNG strength.

        Assumes that the default DRBG is CTR_DRBG.
        """
        self.bad_case('''
                      #undef MBEDTLS_PSA_CRYPTO_RNG_STRENGTH
                      #define MBEDTLS_PSA_CRYPTO_RNG_STRENGTH 256
                      #define MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
                      ''',
                      error=r'strength.*128-bit AES')

    def test_rng_strength_default_aes_128(self) -> None:
        """Check that AES-128 is flagged as not achieving the default RNG strength.

        Assumes that the default DRBG is CTR_DRBG.
        """
        self.bad_case('''
                      #define MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH
                      ''',
                      error=r'strength.*128-bit AES')

    def test_rng_strength_256_sha_1(self) -> None:
        """Check that SHA-1 is flagged as not achieving 256-bit strength.
        """
        self.bad_case('''
                      #undef MBEDTLS_PSA_CRYPTO_RNG_STRENGTH
                      #define MBEDTLS_PSA_CRYPTO_RNG_STRENGTH 256
                      #define MBEDTLS_PSA_CRYPTO_RNG_HASH PSA_ALG_SHA_1
                      ''',
                      # Currently rejected with a generic check (only
                      # whitelisted hash algorithms are allowed).
                      # If we remove the whitelist, this should still be
                      # rejected because the hash is too small.
                      error=r'Invalid hashing algorithm for MBEDTLS_PSA_CRYPTO_RNG_HASH')

    def test_rng_entropy_no_source(self) -> None:
        """Removed all default entropy sources."""
        self.bad_case('''
                      #undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
                      ''',
                      error=r'no sources')

    def test_rng_entropy_nv_seed_only_unconfirmed(self) -> None:
        """Removed all default entropy sources and added NV seed.

        This should be rejected because it is a somewhat insecure
        configuration that may have arisen accidentally.
        """
        self.bad_case('''
                      #undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
                      #define MBEDTLS_ENTROPY_NV_SEED
                      ''',
                      error=r'no true sources')

    def test_rng_entropy_nv_seed_only_acknowledged(self) -> None:
        """Removed all default entropy sources and added NV seed.

        Check that this is ok when explicitly acknowledged.
        """
        self.good_case('''
                       #undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
                       #define MBEDTLS_ENTROPY_NV_SEED
                       #define MBEDTLS_ENTROPY_NO_SOURCES_OK
                       ''')

    def test_rng_external_rng_with_nv_seed(self) -> None:
        """Activated the external RNG and the NV seed.

        Currently should result in an error, because the NV seed would not
        be used, so the security would be lower than users might expect.
        If we implement the NV seed together with the external RNG,
        this should become a good case.
        """
        self.bad_case('''
                      #define MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG
                      #define MBEDTLS_ENTROPY_NV_SEED
                      ''',
                      error=(
                          r'MBEDTLS_ENTROPY_NV_SEED has no effect when MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG is enabled' # pylint: disable=line-too-long
                      ))


if __name__ == '__main__':
    unittest.main()
