#!/usr/bin/env python

"""
<Program Name>
  check_public_interfaces.py

<Author>
  Joshua Lock <jlock@vmware.com>

<Started>
  January 6, 2020.

<Copyright>
  See LICENSE for licensing information.

<Purpose>
  Public facing modules (e.g. interface.py and keys.py) must be
  importable, even if the optional dependencies are not installed.

  Each public facing function should always be callable and present
  meaningful user-feedback if an optional dependency that is required for
  that function is not installed.

  This test purposefully only checks the public functions with a native
  dependency, to avoid duplicated tests.

  NOTE: the filename is purposefully check_ rather than test_ so that test
  discovery doesn't find this unittest and the tests within are only run
  when explicitly invoked.
"""

import os
import shutil
import tempfile
import unittest
from unittest import mock

import securesystemslib.exceptions  # pylint: disable=wrong-import-position
import securesystemslib.gpg.constants  # pylint: disable=wrong-import-position
import securesystemslib.gpg.functions  # pylint: disable=wrong-import-position
import securesystemslib.gpg.util  # pylint: disable=wrong-import-position
import securesystemslib.interface  # pylint: disable=wrong-import-position
import securesystemslib.keys  # pylint: disable=wrong-import-position


class TestPublicInterfaces(
    unittest.TestCase
):  # pylint: disable=missing-class-docstring
    @classmethod
    def setUpClass(cls):
        cls.temp_dir = tempfile.mkdtemp(dir=os.getcwd())

    @classmethod
    def tearDownClass(cls):
        shutil.rmtree(cls.temp_dir)

    def test_interface(self):

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface._generate_and_write_rsa_keypair(  # pylint: disable=protected-access
                password="pw"
            )

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_rsa_keypair("pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_rsa_keypair("pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            # Mock entry on prompt which is presented before lower-level functions
            # raise UnsupportedLibraryError
            with mock.patch(
                "securesystemslib.interface.get_password", return_value=""
            ):
                securesystemslib.interface.generate_and_write_rsa_keypair_with_prompt()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_unencrypted_rsa_keypair()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            path = os.path.join(self.temp_dir, "rsa_key")
            with open(path, "a"):  # pylint: disable=unspecified-encoding
                securesystemslib.interface.import_rsa_privatekey_from_file(path)

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface._generate_and_write_ed25519_keypair(  # pylint: disable=protected-access
                password="pw"
            )

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_ed25519_keypair("pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            # Mock entry on prompt which is presented before lower-level functions
            # raise UnsupportedLibraryError
            with mock.patch(
                "securesystemslib.interface.get_password", return_value=""
            ):
                securesystemslib.interface.generate_and_write_ed25519_keypair_with_prompt()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_unencrypted_ed25519_keypair()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            path = os.path.join(self.temp_dir, "ed25519_priv.json")
            with open(path, "a") as f:  # pylint: disable=unspecified-encoding
                f.write("{}")
                securesystemslib.interface.import_ed25519_privatekey_from_file(
                    path, "pw"
                )

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface._generate_and_write_ecdsa_keypair(  # pylint: disable=protected-access
                password="pw"
            )

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_ecdsa_keypair("pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            # Mock entry on prompt which is presented before lower-level functions
            # raise UnsupportedLibraryError
            with mock.patch(
                "securesystemslib.interface.get_password", return_value=""
            ):
                securesystemslib.interface.generate_and_write_ecdsa_keypair_with_prompt()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.interface.generate_and_write_unencrypted_ecdsa_keypair()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            path = os.path.join(self.temp_dir, "ecddsa.priv")
            with open(path, "a") as f:  # pylint: disable=unspecified-encoding
                f.write("{}")
                securesystemslib.interface.import_ecdsa_privatekey_from_file(
                    path, password="pw"
                )

    def test_keys(self):
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.generate_rsa_key()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.generate_ecdsa_key()

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.generate_ed25519_key()

        data = "foo"
        keydict = {
            "keytype": "ed25519",
            "scheme": "ed25519",
            "keyid": "f00",
            "keyval": {"private": "f001", "public": "b00f"},
        }
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.create_signature(keydict, data)

        keydict["keytype"] = "sphincs"
        keydict["scheme"] = "sphincs-shake-128s"
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.create_signature(keydict, data)

        keydict["keytype"] = "ecdsa"
        keydict["scheme"] = "ecdsa-sha2-nistp256"
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.create_signature(keydict, data)

        keydict["keytype"] = "rsa"
        keydict["scheme"] = "rsassa-pss-sha256"
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.create_signature(keydict, data)

        keydict["keytype"] = "ecdsa"
        keydict["scheme"] = "ecdsa-sha2-nistp256"
        sig = {
            "keyid": "f00",
            "sig": "cfbce8e23eef478975a4339036de2335002d57c7b1632dd01e526a3bc52a5b261508ad50b9e25f1b819d61017e7347e912db1af019bf47ee298cc58bbdef9703",
        }
        # NOTE: we don't test ed25519 keys as they can be verified in pure python
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.verify_signature(keydict, sig, data)

        keydict["keytype"] = "sphincs"
        keydict["scheme"] = "sphincs-shake-128s"
        sig = {
            "keyid": "f00",
            "sig": "A" * 7_856,
        }
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.verify_signature(keydict, sig, data)

        keydict["keytype"] = "rsa"
        keydict["scheme"] = "rsassa-pss-sha256"
        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.verify_signature(keydict, sig, data)

        priv = "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----\n"

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.import_rsakey_from_private_pem("")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.encrypt_key(keydict, "foo")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.decrypt_key("enc", "pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.create_rsa_encrypted_pem(priv, "pw")

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.import_ed25519key_from_private_json(
                "".encode("utf-8"), ""
            )

        with self.assertRaises(
            securesystemslib.exceptions.UnsupportedLibraryError
        ):
            securesystemslib.keys.import_ecdsakey_from_private_pem(priv)

    def test_purepy_ed25519(self):
        data = b"The quick brown fox jumps over the lazy dog"
        pub = b"\xbe\xb7\\&\x82\x06UN\x96<E\xdc\xbf<\x00A@\xd1\xcbi\xbb\xfe\x93p\xefso\x198\x8c\x9b&"
        sig = b".\xc7\xa5\xe2\x95\xfabe\xe1\x0f=\xa7\xf1\xa42\xe7t/\x04\x1f\x08\x1bO\xae\xca\xb3\xa1+\xf0\xfc\x8f6l\x91\x9c\x90\xc2g\xe9\xed\x1d\xfd\xebzuV\xb9Y\xa9m\xd0\xdc\xfe\xa1}\xa3Xb-9\xaf6\xbf\t"

        valid = securesystemslib.ed25519_keys.verify_signature(
            pub, "ed25519", sig, data
        )
        self.assertEqual(True, valid)

        bsig = b"\xd3/\x7f\x7f\xa5;6Pq\x14f]\x8b\x0e@\x8a:\xc2\xa1\xb8\xee\x11\xef\x06s\x12\xa9\x0b0\xe9@\xd5Q\xb6\xf7\xe7\xb9\xf6\xc7J\x99_L\x01\xf7\xcdi\x05\xea\xdf\x05D\x12\x1f\xeeT\xe1y\xb1\x9a\x8e\xebS\x04"
        invalid = securesystemslib.ed25519_keys.verify_signature(
            pub, "ed25519", bsig, data
        )
        self.assertEqual(False, invalid)

    def test_gpg_functions(self):
        """Public GPG functions must raise error on missing cryptography lib."""
        expected_error = securesystemslib.exceptions.UnsupportedLibraryError
        expected_error_msg = securesystemslib.gpg.functions.NO_CRYPTO_MSG

        with self.assertRaises(expected_error) as ctx:
            securesystemslib.gpg.functions.create_signature("bar")
        self.assertEqual(expected_error_msg, str(ctx.exception))

        with self.assertRaises(expected_error) as ctx:
            securesystemslib.gpg.functions.verify_signature(None, "f00", "bar")
        self.assertEqual(expected_error_msg, str(ctx.exception))

        with self.assertRaises(expected_error) as ctx:
            securesystemslib.gpg.functions.export_pubkey("f00")
        self.assertEqual(expected_error_msg, str(ctx.exception))


if __name__ == "__main__":
    unittest.main(verbosity=1, buffer=True)
