"""Test cases for "signer.py"."""

import copy
import os
import shutil
import tempfile
import unittest
from contextlib import suppress
from pathlib import Path
from typing import Any, Optional

from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes
from cryptography.hazmat.primitives.serialization import (
    load_pem_private_key,
    load_pem_public_key,
)

from securesystemslib._gpg.constants import have_gpg
from securesystemslib.exceptions import FormatError, UnverifiedSignatureError
from securesystemslib.signer import (
    KEY_FOR_TYPE_AND_SCHEME,
    SIGNER_FOR_URI_SCHEME,
    CryptoSigner,
    GPGKey,
    GPGSigner,
    Key,
    SecretsHandler,
    Signature,
    Signer,
    SpxKey,
    SpxSigner,
    SSlibKey,
    generate_spx_key_pair,
)
from securesystemslib.signer._utils import compute_default_keyid

PEMS_DIR = Path(__file__).parent / "data" / "pems"


class TestKey(unittest.TestCase):
    """Key tests. See many more tests in python-tuf test suite"""

    def test_key_from_to_dict(self):
        """Test to/from_dict for known keytype/scheme combos"""
        for (keytype, scheme), key_impl in KEY_FOR_TYPE_AND_SCHEME.items():
            keydict = {
                "keytype": keytype,
                "scheme": scheme,
                "extra": "somedata",
                "hashes": ["only recognized by GPGKey"],
                "keyval": {
                    "public": "pubkeyval",
                    "foo": "bar",
                },
            }

            key = Key.from_dict("aa", copy.deepcopy(keydict))
            self.assertIsInstance(key, key_impl)
            self.assertDictEqual(keydict, key.to_dict())

    def test_sslib_key_from_dict_invalid(self):
        """Test from_dict for invalid data"""
        invalid_dicts = [
            {"scheme": "ed25519", "keyval": {"public": "abc"}},
            {"keytype": "ed25519", "keyval": {"public": "abc"}},
            {"keytype": "ed25519", "scheme": "ed25519"},
            {"keytype": "ed25519", "scheme": "ed25519", "keyval": {"x": "y"}},
            {
                "keytype": "ed25519",
                "scheme": "ed25519",
                "keyval": {"public": b"abc"},
            },
        ]
        for keydict in invalid_dicts:
            with self.assertRaises((KeyError, ValueError)):
                Key.from_dict("aa", keydict)

    def test_key_verify_signature(self):
        ed25519_keyid = (
            "fc3920f44a1deec695ed9327f70513909a36f51ad19774167ddf28a12f8bbbed"
        )
        ed25519_pub = "50a5768a7a577483c28e57a6742b4d2170b9be628a961355ef127c45f2aefdc5"
        rsa_keyid = "b7c94258646e970d336b779eea6b90ef931ea56e2d356ce487201f6bb776e94b"
        rsa_pub = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsDqUoiFJZX+5gm5pyI1l\nVc/N3yjJVOIl9GyiK0mRyzV3IzUQzhjq8nhk0eLfzXw2XwIAYOJC6dR/tGRG4JDx\nJkez5FFH4zLosr/XzT7CG5zxJ3kKICLD1v9rZQr5ZgARQDOpkxzPz46rGnE0sHd7\nMpnpPMScA1pMIzwM1RoPS4ntZipI1cl9M7HMQ6mkBp8/DNKCqaDWixJqaGgWrhhK\nhI/1mzBliMKriNxPKSCGVlOk/QpZft+y1fs42s0DMd5BOFBo+ZcoXLYRncg9S3A2\nxx/jT69Bt3ceiAZqnp7f6M+ZzoUifSelaoL7QIYg/GkEl+0oxTD0yRphGiCKwn9c\npSbn7NgnbjqSgIMeEtlf/5Coyrs26pyFf/9GbusddPSxxxwIJ/7IJuF7P1Yy0WpZ\nkMeY83h9n2IdnEYi+rpdbLJPQd7Fpu2xrdA3Fokj8AvCpcmxn8NIXZuK++r8/xsE\nAUL30HH7dgVn50AvdPaJnqAORT3OlabW0DK9prcwKnyzAgMBAAE=\n-----END PUBLIC KEY-----"
        ecdsa_keyid = "985171ff9ee901fbab17aa6f57347933aeae9d194f0f93e83e5c3dbc1755e754"
        ecdsa_pub = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsYJfSlYU3UlYbGOZfE/yOHkayWWq\nLPR/NeCa83szZmnJGc9wwCRPvJS87K+eDGIhhhKueTyrLqXQqmyHioQbOQ==\n-----END PUBLIC KEY-----\n"
        ecdsa_nistp384_keyid = (
            "0155661bdf705f621a74f55eef36c9ae041e456141eced7a45d4a1f75ded9ac0"
        )
        ecdsa_nistp384_pub = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEksAG80nLUksODTEUBTPJJPYN0bfxhkrr\n2hlyokfRG4kDYsRRN86vWwxDTW7qhWNZPFhJMJxHmvHsCbLz/IF7hdo8Xv/vRO4M\nVHbwq0fiWznUvkZowHC5fH2EEvNF1R5t\n-----END PUBLIC KEY-----\n"
        ecdsa_nistp521_keyid = (
            "3dddef3c311b151c5fe12f5fcfa3d9795e2b5fe1a6746c2691befacb13a6af4a"
        )
        ecdsa_nistp521_pub = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBXxFGyw9J9C43woa6huZ3IwrEZhku\neHDNZ1UqnHcFhNYFhM7witKxgv6OUpwlwPsPSUjqcBxkkpay4rT4UTwIP94AdsQL\nq3DQ+BuwrEELjAAMxvQGfoMlLJ5fHM4p4KhlKwpk2HiBQp4r1bjuA6MFq7s5yF0C\nnb9TH+ZZwV5/cjCewxY=\n-----END PUBLIC KEY-----"

        key_sig_data = [
            (
                ed25519_keyid,
                "ed25519",
                "ed25519",
                ed25519_pub,
                "dbea1238620949daeb806b0347ffd9f28f8e481edc9ffb4dd8189715c0219f195f68f91ea72769fd8892d6b0af8884b4ad49ea09510201dae6ed0995075ea103",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsassa-pss-sha224",
                rsa_pub,
                "78fb3c56142868a5de1bd965af37f204763eac4755ea9b79911bac89159a98679987a8b99356987524cd9108a5f373f367aac8ba970d7b690a67b8fb79893d07fcf4a66569fcc479578c633302a95e7e4640f6f88d5e0d5e26af7497f613f6417bc30df6377137f63f167ee886d2e4c32a2a945a3e08a8d68630c9d9e57e8650cf5e501516b61bdb4d00de3b4046ae413e5a583ac2dbd885e40b8d8180e51ebfaab2f5d2b95e6b5b093e2511e5893997adb8f9ee233a4e40e902d60d6fbab7df04b97d9872df6081661b2e2bd0663019daee059eacaa9cebf8efde9ca9f1a22e7cfb6b907bcab29c4d4318e214d91b1ca1eb5977ecec45219472189a6720ec956e29716a0d82b9ba3c4a2c5d2187295c4f7a4e20b622081b38b2c2e315f168aea7bd864c5b8c30f07235145682de98c0f28784c515ae096dd68548107fc7c5b7a01286625dd40870338921036dfa07932cad4f6e42a939867b8aaf7e8e3e0ba03a1005412832ce3f53d9576b68938d57b87d0455d0de0d97e12132ac4f8dee2b",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsassa-pss-sha256",
                rsa_pub,
                "2f3ed7aa4d18fdb2a677d7a772f3b1283947b09b834a6d523a66b734404552ce4966e1984d4f4a46bc20f5effbc509bbf953469feb6eda5230e5bbe3399839e0d5c601e853918427849f2d90939a843527193932aef4711b65db353bc4adf76d4ec67886d6cbb2e2b315148deee649be7489fb09439a0d22b1abf4b4faa118dd8682f7be173ea398ecf0905eb8eb7232909492ceed1a3134fb678c10b0307ac9f5924b73399d7ac6e729c8e91eca6a0e541dafd22d5fc774fa900c18e38b854884342864951d05b519abf7cee8afe761334c546491b9a09545a9eb5a4096ac5e4be35b794fb9cb8c5204ddbc5eb7d1fc92c2f221d99d49909a41cd747e7f436c07480efb7e94f52d8a8641f9a32d2fa9e2e7f862537b66fa07ba56f44ce7d1f2ad7637ebd8c454d904eea09121977633bcef5748ccc843bc4ebe720878ad9caca97c714aeafeffffd9d1c0cd92218e78691c3abab43958f2defd39381bde71ed73de23044bf44791f8717e1d2150bb5fe7fe0184d089042c284e9ca5779276fc",
            ),
            # Test sig with max salt length (briefly available in v0.24.0)
            (
                rsa_keyid,
                "rsa",
                "rsassa-pss-sha256",
                rsa_pub,
                "392c5017fd389863649fcda8ba054e1bb346c2ed5cc9959c897a731fc37cd3401c15d77ed17f040a70517c9256f2682181da9aedca08bf3c2aac26658d3064c7df73365da6b47b8f02cb18813cb899e26a101b82e45752cba556e9fa8ad224bb363efa1db9209873ec82ca74bcb7109facfc29f45e5521a6a856803b1a221609f711da2b93915a65d6bfb1f5635a5bc7bdb98510b824fad243c0eaa4ac6674e492d10c25a9e442e9ec6e72871b5d67dfc20b1d5a76ab5f357a6cba4ae9587009744a8023a71c9da38e7ed9c1264c649664c8b72593b79ecc4d0f76d9ad539ace51dd73e9bbf11535f17d6d4caae576f67d1b203a08d35a823eb7c2df99675d4b6651d647f29a6e179263e1a18eb03a6c9209d1daf40c465c19052d46e8e6f5bb480309b91b064e127ea20812e5c0b4fc7ddd98f401a1b920866543335ac31ea8cd650341ef321ce98c3ba48c2ea9172da3a614b8791f98ac7dffd6e2e8506694df100903d60dffa6cf4f6723724ba71aba462229d5f62b7e007553b8af54dabc",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsassa-pss-sha384",
                rsa_pub,
                "1c2613ec1f07eacc74b77d0d390eccd25f035d5e973c31471b0521adc3ed3344cdb86a4cf7b73eacabc302198c1b064d4deea60b57dcff5d6b2717d37fff18fb8aa49d7fe07665f452c8a91e7aca11623a314262b3f829930fe65e574e12373737cfa2e15c72931309cb63624d93f62e4819ca449d33cdbfbdb737fe52f24c1bcf1d52b7b9bedd0770f8722ce5b0e273258450b2524d620c44d120a4a42a744880ecc9fbdee831374e9dd968128c10963ae2f43b53355ca5fb389d63895ffd318752ea4293fde548f50c06b9a71b4064f2646f2f10f1ee0df5f3a93ae0bd89e44dd9f595591ca79e9aa8a72b983de1019bbeb2746dbb4895154bcdd4b8bca8626b558b4e83aefdc2dbf5f7c5cd930303f18a49ddd52f41a8b4aaef61cc832f7d33def162e9a4a405ee4212bcde1cf7b186032f1edc4e089ff502e200282e2242e09771f0e6fa4e754150330f5f68df1ce58c85a1617b5f1cedfe9e7f1f1fa1e1ae8429bab86d8f17757daee028c9c38190dee71372a7a4003971f147f27bc75b",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsassa-pss-sha512",
                rsa_pub,
                "050f41edfd08249c9824f9940ef25ba1c2fae475bf4700bff3b4c2f209b5651e583892c5d3aa4b79d8f5e1d5b1805195fc96b7664e349509d2f5da366ac5a93641b46accc7571ecacb5472b8bdd16e2128ba237e6688244e3d65bbe3417d14e86ab4ff7e8b13e3fe0632c6c8222449986bdc432b0816cfc59c439e408492b63668269d300da7a59f6e4bcb541414b6b4bafdee62d7c9a6390c52a077a3c1585243bf1bc4319972cdb27e790461309ca83d607c37690b546489e7710fadb0c454ca0b2dc32c2d8c2ac210f5bfe1b278f2879f77186d0f3d228b9fa7d4eff7d89939eddc3acc37f513221d12ed7f84aeeb51516b7e001eae1ffb2046c0ea663f15cdfc5eb7651c9b7bee6468c25606e077cde95ade93a79d2c2ad42dec358e839582a3fd9b196a5dd0e20585f1d8c7c99692bc95ee5d33e064651bb0d70d1605390dfca5f907499897630a3a5229532c7cd6a57db102566bc2644485ba7aba5b40eee91d1fa64421a618fed90fc29168e5098ee1f27f893eb758f5c4087bd94b10",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsa-pkcs1v15-sha224",
                rsa_pub,
                "9d4eb41f187a44f384b0555bfef170428b0a46c6111f67104fec8843a9a3c91b8522b36f5bbfd5240a9871ffdc4f44faf33a4870c3daa5c2377ebcbeeab6aeb991d13840abd68f166631fe356e6dc0411af05a94a263d52805eacfe2c5fd3ce291898c6550427cd2ef79155331714eb553209bfb49f921c7aea46a2fa113bb5771a51f569dcb3c5766a8e0867172e9b1a914a0f1235145c33afb9532f63dc04f919c99347c7fca5eac6e3ab338a2a73b3c9e0ba19bb1454cd10b6ebbfe274daa8121681de84fa3d30a70e168e342748ab5d95f82a2bc74d287e89ef4031b7fc4b0fc59a5e5cdd792ae9c569d2de9463d09b45abc16ee4578bafeb32771aee4f5d777e597ec40fa155f2c8d05460ca32f0ae8e97e958420dc830ba33c3a53b0f3f4e68dfd0675f9b2eb71df9dbee695cb7700f05db0cecc6803cafa152774c0edca6617a734c4453248a584c35b2ec2aa0b8dfb196e67c5241c924d67586085a38b0f0210dbac724973a625cadce95527815bd01e1c1bbd9fd63e7da920f7850f",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsa-pkcs1v15-sha256",
                rsa_pub,
                "0c8ec1336540b7e04a2cef460597c54a586766725c532cbd13b3c7071018179e666b75f3165f805c677c5a6169097b4c20175fbea8a0aa4879ecb465cebe2040f6b8db4caa0965b56a20e39bea0c5a8f3746284e41239cd0ab998c5d70b7a0a26f1f5c2909461ab74941f8dcde8faa4d360be710cc405664e5b7a62b54509b32a80ac58b4c494e54270a9d51f8861c7eeb984898414507365bf4bf2f07e4e0507c1f01ee6560dca6f27bef79abd2c9e18bc7cf8820d017f3ad51ce82f6d9bc57e7c3ffc1ca13a9c83a8163c1c0850a7145b577d1db99634435e7b74de6fb29ec68ee13a79e16e5c00eae0d9a15b6cb4e81770fd8d0c40a80f3df4c6d2b9c29c4b35645914263d0aa91d6700ae6f23a7c16d3ab6095c7b736b34ea6b971937f49fad55145e41c6a77e67fbabd2870c8707e0b36fc6bcb8eba9889231df7153951a03e81478eb9bacde6e77eaa683f2133186bb7987f670b602d3902068b8a4a50e1de8334eeb411530599fa35ee37d1f678bf5277bf46cd30ea7bfe79e5161560",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsa-pkcs1v15-sha384",
                rsa_pub,
                "7eb62b42cafda8fadfbc957d759a3f09089c28132389e1a1d2fb50a8cdb7491e07bf351f03a442e36d7a5abe0daeec621a1e414827dbeac4bd8f6ede709801afec1969b903212995f56f7c544b35719f8819a340872530b4f087ab8159bb052e6d0a99ed65ed2114de0044a38cbb017a67fe3f41854fb95e5218d8d4d77d3e51192075b4f8bd38926484116642545225f7389b5bcd61baa49bdddaf8f499a1c3ef72a4e2e71767f56863416a13f4d5c4fb2a876a89e8c40ac12f7b188b971257a7fc4c48c6f4942e85120402e3c4c2c4761638a57c3f59cf4e136e64a755c68075ec85d49cb89d8f6ec2b348c40f3e46cf3fd9d93e5721e26bdd9ee2eadcc13a757cdf5d5ca40e59bf48e737e5341708adf9d306d123c50f08ebba1bec5cc233b9fd2dc5bffee179f40cf88648218710ec2bf273fb0d6f02cee3eedd19a0ca0b64f60991f9d4dff97cc088d6db9106c176cf48e12183c3ca3ff0acf2948e3fec8c82582d84e70342b0767c7b62aa2d92b9441b2bbdf7a00ec827fa0084361f92",
            ),
            (
                rsa_keyid,
                "rsa",
                "rsa-pkcs1v15-sha512",
                rsa_pub,
                "16f7e8877944cedc0bb8cf45c04a2dcee7e844f8aed71e8d10c8950b509be9959a3b0b7ad41db44bfaeebc1e00fc9d25fc05c477ae0eebc49aaba4a6f2f06e768452d2e3c6dd3401b78f945402a72390729de99fc3ef414f9cb9f9ed3052e26615517d3a82c30ac531a516a391867329f48fd6cbacb0ae4532bd9146879abd677a871bd80d45bdc596ce828d6a2bb6f40c8390978b336cc9d32634d2653f6a4f96f38799cd96931a9a0ac23834cc73de19ca804d683abff0c962cd77573b4f6df71a136be2f7eb3fd80e3d7eb74c3b56a21db49b3e3ac355492ba3a63c73f94533fc5c8567380e64b056a2199212fc3cf5f92a6b8514773bf2847b6598b3433297d282a74cdf21108dcd995761b47655be41dcefbb6e223778f6de8ade781d53f44c9767d77b3c41d43ddb9cc01eaa4db79a4698a30b5d8d00b8e7c7106a58d8dc9c2bb0a35f2fc11647727b2fd1ab254b288f1120ef8bf66b73617a7de4a7e3943e2ca921f55e08ffb7ed1138bd2e61f922659e1fe8bd21b9e39fe98ecf273c",
            ),
            (
                ecdsa_keyid,
                "ecdsa",
                "ecdsa-sha2-nistp256",
                ecdsa_pub,
                "3045022100b3db1e5ca53226ee27f93a3b2f5e1534f4a4c51f872aa1b6efd0b37be27f483602204f793f7ad7c25188ad55eb6a8a8d142f89b0b0090815de47d9a24389872b02e3",
            ),
            (
                ecdsa_nistp384_keyid,
                "ecdsa",
                "ecdsa-sha2-nistp384",
                ecdsa_nistp384_pub,
                "30650230747ffab40d894dcacfa2da613a312b2423aa744c7b2365345467819c2af210983c5a7ce6810db995f2e6c90f90f9ad1c023100fdde5013796916af1989b757b64f2fad8496fa7b2b19e9568260868d4a670e6feb123b3337179a4d06b5fbe42b6937fb",
            ),
            (
                ecdsa_nistp521_keyid,
                "ecdsa",
                "ecdsa-sha2-nistp521",
                ecdsa_nistp521_pub,
                "3081880242019c9621491de75e9b89c4956f6571840528923db953a0fdcf88f9c0221e8c181459af1d9f4e420ad7b971c5c429e457f1f67cfc968c655ea569c3185bfeb2bd943f024201bd2d51bee9ae0bbeb79c99c7bc1758dd256ce7caad9020fb2e2873efc80613826a45b96be25eab11dc521942b92af5820ddde08797d9d6ae236aa54036b41e1152",
            ),
            (
                ecdsa_keyid,
                "ecdsa-sha2-nistp256",
                "ecdsa-sha2-nistp256",
                ecdsa_pub,
                "304502207d0058b745b2259501204c2ba287ba3769ec2420e12463a325c59670c24df9b6022100836ca63a1b870f755c1596711a003a505e72e25cb0970e823a331e044adc63ec",
            ),
            (
                ecdsa_nistp384_keyid,
                "ecdsa-sha2-nistp384",
                "ecdsa-sha2-nistp384",
                ecdsa_nistp384_pub,
                "30650230747ffab40d894dcacfa2da613a312b2423aa744c7b2365345467819c2af210983c5a7ce6810db995f2e6c90f90f9ad1c023100fdde5013796916af1989b757b64f2fad8496fa7b2b19e9568260868d4a670e6feb123b3337179a4d06b5fbe42b6937fb",
            ),
            (
                ecdsa_nistp521_keyid,
                "ecdsa-sha2-nistp521",
                "ecdsa-sha2-nistp521",
                ecdsa_nistp521_pub,
                "3081880242019c9621491de75e9b89c4956f6571840528923db953a0fdcf88f9c0221e8c181459af1d9f4e420ad7b971c5c429e457f1f67cfc968c655ea569c3185bfeb2bd943f024201bd2d51bee9ae0bbeb79c99c7bc1758dd256ce7caad9020fb2e2873efc80613826a45b96be25eab11dc521942b92af5820ddde08797d9d6ae236aa54036b41e1152",
            ),
        ]
        for keyid, keytype, scheme, pub, sig in key_sig_data:
            key = Key.from_dict(
                keyid,
                {
                    "keytype": keytype,
                    "scheme": scheme,
                    "keyval": {
                        "public": pub,
                    },
                },
            )

            sig = Signature.from_dict(  # noqa: PLW2901
                {
                    "keyid": keyid,
                    "sig": sig,
                }
            )

            key.verify_signature(sig, b"DATA")
            with self.assertRaises(UnverifiedSignatureError, msg=scheme):
                key.verify_signature(sig, b"NOT DATA")

    def test_unsupported_key(self):
        keydict = {
            "keytype": "custom",
            "scheme": "ed25519",
            "keyval": {
                "public": "8ae43d22b8e0fbf4a48fa3490d31b4d389114f5dc1039c918f075427f4100759",
            },
        }
        with self.assertRaises(ValueError):
            Key.from_dict(
                "e33221e745d40465d1efc0215d6db83e5fdb83ea16e1fb894d09d6d96c456f3b",
                keydict,
            )

    def test_custom_key(self):
        class CustomKey(SSlibKey):
            """Fake keytype that actually uses ed25519 under the hood"""

            @classmethod
            def from_dict(cls, keyid: str, key_dict: dict[str, Any]) -> "CustomKey":
                assert key_dict.pop("keytype") == "custom"
                keytype = "ed25519"
                scheme = key_dict.pop("scheme")
                keyval = key_dict.pop("keyval")
                return cls(keyid, keytype, scheme, keyval, key_dict)

            def to_dict(self) -> dict[str, Any]:
                return {
                    "keytype": "custom",
                    "scheme": self.scheme,
                    "keyval": self.keyval,
                    **self.unrecognized_fields,
                }

        # register custom key type
        KEY_FOR_TYPE_AND_SCHEME[("custom", "ed25519")] = CustomKey

        # setup
        sig = Signature.from_dict(
            {
                "keyid": "e33221e745d40465d1efc0215d6db83e5fdb83ea16e1fb894d09d6d96c456f3b",
                "sig": "3fc91f5411a567d6a7f28b7fbb9ba6d60b1e2a1b64d8af0b119650015d86bb5a55e57c0e2c995a9b4a332b8f435703e934c0e6ce69fe6674a8ce68719394a40b",
            }
        )

        keydict = {
            "keytype": "custom",
            "scheme": "ed25519",
            "keyval": {
                "public": "8ae43d22b8e0fbf4a48fa3490d31b4d389114f5dc1039c918f075427f4100759",
            },
        }
        key = Key.from_dict(
            "e33221e745d40465d1efc0215d6db83e5fdb83ea16e1fb894d09d6d96c456f3b",
            keydict,
        )

        # test that CustomKey is used and that it works
        self.assertIsInstance(key, CustomKey)
        key.verify_signature(sig, b"DATA")
        with self.assertRaises(UnverifiedSignatureError):
            key.verify_signature(sig, b"NOT DATA")

        del KEY_FOR_TYPE_AND_SCHEME[("custom", "ed25519")]


class TestSSlibKey(unittest.TestCase):
    """SSlibKey tests."""

    def test_from_crypto(self):
        """Test load pyca/cryptography public key for each SSlibKey keytype"""
        test_data = [
            (
                "rsa",
                "rsassa-pss-sha256",
                "2f685fa7546f1856b123223ab086b3def14c89d24eef18f49c32508c2f60e241",
                "rsa_public.pem",
            ),
            (
                "ecdsa",
                "ecdsa-sha2-nistp256",
                "50d7e110ad65f3b2dba5c3cfc8c5ca259be9774cc26be3410044ffd4be3aa5f3",
                "ecdsa_public.pem",
            ),
            (
                "ecdsa",
                "ecdsa-sha2-nistp384",
                "0155661bdf705f621a74f55eef36c9ae041e456141eced7a45d4a1f75ded9ac0",
                "ecdsa_secp384r1_public.pem",
            ),
            (
                "ecdsa",
                "ecdsa-sha2-nistp521",
                "3dddef3c311b151c5fe12f5fcfa3d9795e2b5fe1a6746c2691befacb13a6af4a",
                "ecdsa_secp521r1_public.pem",
            ),
            (
                "ed25519",
                "ed25519",
                "c6d8bf2e4f48b41ac2ce8eca21415ca8ef68c133b47fc33df03d4070a7e1e9cc",
                "ed25519_public.pem",
            ),
        ]

        def _from_file(path):
            with open(path, "rb") as f:
                pem = f.read()

            crypto_key = load_pem_public_key(pem)
            return crypto_key

        for keytype, default_scheme, default_keyid, fname in test_data:
            crypto_key = _from_file(PEMS_DIR / fname)
            key = SSlibKey.from_crypto(crypto_key)
            self.assertEqual(key.keytype, keytype)
            self.assertEqual(key.scheme, default_scheme)
            self.assertEqual(key.keyid, default_keyid)

        # Test with non-default scheme/keyid
        crypto_key = _from_file(PEMS_DIR / "rsa_public.pem")
        key = SSlibKey.from_crypto(
            crypto_key,
            scheme="rsa-pkcs1v15-sha224",
            keyid="abcdef",
        )
        self.assertEqual(key.scheme, "rsa-pkcs1v15-sha224")
        self.assertEqual(key.keyid, "abcdef")

    def test_verify_invalid_keytype_scheme(self):
        rsa = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsDqUoiFJZX+5gm5pyI1l\nVc/N3yjJVOIl9GyiK0mRyzV3IzUQzhjq8nhk0eLfzXw2XwIAYOJC6dR/tGRG4JDx\nJkez5FFH4zLosr/XzT7CG5zxJ3kKICLD1v9rZQr5ZgARQDOpkxzPz46rGnE0sHd7\nMpnpPMScA1pMIzwM1RoPS4ntZipI1cl9M7HMQ6mkBp8/DNKCqaDWixJqaGgWrhhK\nhI/1mzBliMKriNxPKSCGVlOk/QpZft+y1fs42s0DMd5BOFBo+ZcoXLYRncg9S3A2\nxx/jT69Bt3ceiAZqnp7f6M+ZzoUifSelaoL7QIYg/GkEl+0oxTD0yRphGiCKwn9c\npSbn7NgnbjqSgIMeEtlf/5Coyrs26pyFf/9GbusddPSxxxwIJ/7IJuF7P1Yy0WpZ\nkMeY83h9n2IdnEYi+rpdbLJPQd7Fpu2xrdA3Fokj8AvCpcmxn8NIXZuK++r8/xsE\nAUL30HH7dgVn50AvdPaJnqAORT3OlabW0DK9prcwKnyzAgMBAAE=\n-----END PUBLIC KEY-----"
        ed25519 = "50a5768a7a577483c28e57a6742b4d2170b9be628a961355ef127c45f2aefdc5"
        ecdsa_nistp256 = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsYJfSlYU3UlYbGOZfE/yOHkayWWq\nLPR/NeCa83szZmnJGc9wwCRPvJS87K+eDGIhhhKueTyrLqXQqmyHioQbOQ==\n-----END PUBLIC KEY-----\n"
        ecdsa_nistp384 = "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEksAG80nLUksODTEUBTPJJPYN0bfxhkrr\n2hlyokfRG4kDYsRRN86vWwxDTW7qhWNZPFhJMJxHmvHsCbLz/IF7hdo8Xv/vRO4M\nVHbwq0fiWznUvkZowHC5fH2EEvNF1R5t\n-----END PUBLIC KEY-----\n"
        ecdsa_nistp521 = "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBXxFGyw9J9C43woa6huZ3IwrEZhku\neHDNZ1UqnHcFhNYFhM7witKxgv6OUpwlwPsPSUjqcBxkkpay4rT4UTwIP94AdsQL\nq3DQ+BuwrEELjAAMxvQGfoMlLJ5fHM4p4KhlKwpk2HiBQp4r1bjuA6MFq7s5yF0C\nnb9TH+ZZwV5/cjCewxY=\n-----END PUBLIC KEY-----"

        test_data = [
            # bad keytype / scheme
            ("ed25519", "rsassa-pss-sha256", rsa),
            ("ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", ecdsa_nistp256),
            ("ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", ecdsa_nistp384),
            ("ecdsa-sha2-nistp256", "ecdsa-sha2-nistp521", ecdsa_nistp521),
            ("rsa", "ed25519", ed25519),
            # bad key type (pem formatted keys only)
            ("rsa", "rsassa-pss-sha256", ecdsa_nistp256),
            ("ecdsa", "ecdsa-sha2-nistp256", rsa),
            # bad curve (ecdsa keys only)
            ("ecdsa", "ecdsa-sha2-nistp256", ecdsa_nistp384),
            ("ecdsa", "ecdsa-sha2-nistp384", ecdsa_nistp256),
            ("ecdsa", "ecdsa-sha2-nistp521", ecdsa_nistp256),
        ]

        for keytype, scheme, val in test_data:
            key = SSlibKey("fake", keytype, scheme, {"public": val})
            with self.assertRaises(ValueError):
                key._verify(b"fakesig", b"fakedata")


class TestSigner(unittest.TestCase):
    """Test Signer and SSlibSigner functionality"""

    def test_signer_sign_with_incorrect_uri(self):
        pubkey = "fake key"
        with self.assertRaises(ValueError) as ctx:
            Signer.from_priv_key_uri("unknownscheme:x", pubkey)

        self.assertEqual(
            "Unsupported private key scheme unknownscheme", str(ctx.exception)
        )

    def test_signature_from_to_dict(self):
        signature_dict = {
            "sig": "30460221009342e4566528fcecf6a7a5d53ebacdb1df151e242f55f8775883469cb01dbc6602210086b426cc826709acfa2c3f9214610cb0a832db94bbd266fd7c5939a48064a851",
            "keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714",
            "foo": "bar",  # unrecognized_field
        }
        sig_obj = Signature.from_dict(copy.copy(signature_dict))

        # Verify that unrecognized fields are stored correctly.
        self.assertEqual(sig_obj.unrecognized_fields, {"foo": "bar"})

        self.assertDictEqual(signature_dict, sig_obj.to_dict())

    def test_signature_eq_(self):
        signature_dict = {
            "sig": "30460221009342e4566528fcecf6a7a5d53ebacdb1df151e242f55f8775883469cb01dbc6602210086b426cc826709acfa2c3f9214610cb0a832db94bbd266fd7c5939a48064a851",
            "keyid": "11fa391a0ed7a447cbfeb4b2667e286fc248f64d5e6d0eeed2e5e23f97f9f714",
        }
        sig_obj = Signature.from_dict(signature_dict)
        sig_obj_2 = copy.deepcopy(sig_obj)

        self.assertEqual(sig_obj, sig_obj_2)

        # Assert that changing the keyid will make the objects not equal.
        sig_obj_2.keyid = None
        self.assertNotEqual(sig_obj, sig_obj_2)
        sig_obj_2.keyid = sig_obj.keyid

        # Assert that changing the signature will make the objects not equal.
        sig_obj_2.signature = None
        self.assertNotEqual(sig_obj, sig_obj_2)

        # Assert that making sig_obj_2 None will make the objects not equal.
        sig_obj_2 = None
        self.assertNotEqual(sig_obj, sig_obj_2)


@unittest.skipIf(not have_gpg(), "gpg not found")
class TestGPGRSA(unittest.TestCase):
    """Test RSA gpg signature creation and verification."""

    @classmethod
    def setUpClass(cls):
        cls.default_keyid = "8465a1e2e0fb2b40adb2478e18fb3f537e0c8a17"
        cls.signing_subkey_keyid = "c5a0abe6ec19d0d65f85e2c39be9df5131d924e9"

        # Create directory to run the tests without having everything blow up.
        cls.working_dir = os.getcwd()
        cls.test_data = b"test_data"
        cls.wrong_data = b"something malicious"

        # Find demo files.
        gpg_keyring_path = os.path.join(
            os.path.dirname(os.path.realpath(__file__)), "gpg_keyrings", "rsa"
        )

        cls.test_dir = os.path.realpath(tempfile.mkdtemp())
        cls.gnupg_home = "rsa"
        shutil.copytree(gpg_keyring_path, os.path.join(cls.test_dir, cls.gnupg_home))
        os.chdir(cls.test_dir)

    @classmethod
    def tearDownClass(cls):
        """Change back to initial working dir and remove temp test directory."""

        os.chdir(cls.working_dir)
        shutil.rmtree(cls.test_dir)

    def test_gpg_sign_and_verify_object(self):
        """Create a signature using a specific key on the keyring."""

        uri, public_key = GPGSigner.import_(self.signing_subkey_keyid, self.gnupg_home)

        signer = Signer.from_priv_key_uri(uri, public_key)
        sig = signer.sign(self.test_data)

        public_key.verify_signature(sig, self.test_data)

        with self.assertRaises(UnverifiedSignatureError):
            public_key.verify_signature(sig, self.wrong_data)

    def test_gpg_fail_sign_keyid_match(self):
        """Fail signing because signature keyid does not match public key."""
        uri, public_key = GPGSigner.import_(self.default_keyid, self.gnupg_home)
        signer = Signer.from_priv_key_uri(uri, public_key)

        # Fail because we imported main key, but gpg favors signing subkey
        with self.assertRaises(ValueError):
            signer.sign(self.test_data)

    def test_gpg_fail_import_keyid_match(self):
        """Fail key import because passed keyid does not match returned key."""

        # gpg exports the right key, but we require an exact keyid match
        non_matching_keyid = self.default_keyid.upper()
        with self.assertRaises(ValueError):
            GPGSigner.import_(non_matching_keyid, self.gnupg_home)

    def test_gpg_fail_sign_expired_key(self):
        """Signing fails with non-zero exit code if key is expired."""
        expired_key = "e8ac80c924116dabb51d4b987cb07d6d2c199c7c"

        uri, public_key = GPGSigner.import_(expired_key, self.gnupg_home)
        signer = Signer.from_priv_key_uri(uri, public_key)
        with self.assertRaises(OSError):
            signer.sign(self.test_data)

    def test_gpg_signer_load_with_bad_scheme(self):
        """Load from priv key uri with wrong uri scheme."""
        key = GPGKey("aa", "rsa", "pgp+rsa-pkcsv1.5", {"public": "val"})
        with self.assertRaises(ValueError):
            GPGSigner.from_priv_key_uri("wrong:", key)

    def test_gpg_signer_load_with_bad_key(self):
        """Load from priv key uri with wrong pubkey type."""
        key = SSlibKey("aa", "rsa", "rsassa-pss-sha256", {"public": "val"})
        with self.assertRaises(ValueError):
            GPGSigner.from_priv_key_uri("gnupg:", key)

    def test_gpg_signature_legacy_data_structure(self):
        """Test custom fields and legacy data structure in gpg signatures."""
        _, public_key = GPGSigner.import_(self.signing_subkey_keyid, self.gnupg_home)
        signer = GPGSigner(public_key, homedir=self.gnupg_home)
        sig = signer.sign(self.test_data)
        self.assertIn("other_headers", sig.unrecognized_fields)

        sig_dict = GPGSigner._sig_to_legacy_dict(sig)
        self.assertIn("signature", sig_dict)
        self.assertNotIn("sig", sig_dict)
        sig2 = GPGSigner._sig_from_legacy_dict(sig_dict)
        self.assertEqual(sig, sig2)

    def test_gpg_key_legacy_data_structure(self):
        """Test legacy data structure conversion in gpg keys."""
        _, public_key = GPGSigner.import_(self.signing_subkey_keyid, self.gnupg_home)
        legacy_fields = {"keyid", "type", "method"}
        fields = {"keytype", "scheme"}

        legacy_dict = GPGSigner._key_to_legacy_dict(public_key)
        for field in legacy_fields:
            self.assertIn(field, legacy_dict)

        for field in fields:
            self.assertNotIn(field, legacy_dict)

        self.assertEqual(public_key, GPGSigner._key_from_legacy_dict(legacy_dict))

    def test_gpg_key__eq__(self):
        """Test GPGKey.__eq__() ."""
        key1 = GPGKey("aa", "rsa", "pgp+rsa-pkcsv1.5", {"public": "val"})
        key2 = copy.deepcopy(key1)
        self.assertEqual(key1, key2)

        key2.keyid = "bb"
        self.assertNotEqual(key1, key2)

        other_key = SSlibKey("aa", "rsa", "rsassa-pss-sha256", {"public": "val"})
        self.assertNotEqual(key1, other_key)


class TestUtils(unittest.TestCase):
    """Test utility methods."""

    def test_compute_default_keyid(self):
        self.assertEqual(
            compute_default_keyid("rsa", "rsassa-pss-sha256", {"public": "abcd"}),
            "7b56b88ae790729d4e359d3fc5e889f1e0669a2e71a12d00e87473870c73fbcf",
        )

        # Unsupported keys can have default keyids too
        self.assertEqual(
            compute_default_keyid("foo", "bar", {"baz": "qux"}),
            "e3471be0598305190ba82f6f8043f4df52f3fbe471fdc187223bd9ade92abebb",
        )

        # Invalid keys cannot
        with self.assertRaises(FormatError):
            compute_default_keyid("foo", "bar", {"baz": 1.1})


@unittest.skipIf(os.name == "nt", "PySPX n/a on Windows")
class TestSphincs(unittest.TestCase):
    """Test create keys, sign and verify for sphincs keys."""

    def test_sphincs(self):
        """sphincs signer smoketest."""

        # Test create/sign/verify
        public_bytes, private_bytes = generate_spx_key_pair()
        public_key = SpxKey.from_bytes(public_bytes)
        signer = SpxSigner(private_bytes, public_key)
        sig = signer.sign(b"data")
        self.assertIsNone(signer.public_key.verify_signature(sig, b"data"))
        with self.assertRaises(UnverifiedSignatureError):
            signer.public_key.verify_signature(sig, b"not data")

        # Test de/serialization
        self.assertEqual(
            signer.public_key,
            SpxKey.from_dict(signer.public_key.keyid, signer.public_key.to_dict()),
        )


class TestCryptoSigner(unittest.TestCase):
    """CryptoSigner tests"""

    @classmethod
    def setUpClass(cls):
        cls.keys: list[PrivateKeyTypes] = []
        for keytype in ["rsa", "ecdsa", "ed25519"]:
            path = PEMS_DIR / f"{keytype}_private.pem"

            with open(path, "rb") as f:
                data = f.read()

            private_key = load_pem_private_key(data, None)

            cls.keys.append(private_key)

    def test_init(self):
        """Test CryptoSigner constructor."""
        for keytype, private_key in zip(["rsa", "ecdsa", "ed25519"], self.keys):
            # Init w/o public key (public key is created from private key)
            signer = CryptoSigner(private_key)
            self.assertEqual(keytype, signer.public_key.keytype)

            # Re-init with passed public key
            signer2 = CryptoSigner(private_key, signer.public_key)
            self.assertEqual(keytype, signer2.public_key.keytype)

    def test_sign(self):
        rsa_schemes = [
            "rsassa-pss-sha224",
            "rsassa-pss-sha256",
            "rsassa-pss-sha384",
            "rsassa-pss-sha512",
            "rsa-pkcs1v15-sha224",
            "rsa-pkcs1v15-sha256",
            "rsa-pkcs1v15-sha384",
            "rsa-pkcs1v15-sha512",
        ]
        ecdsa_schemes = ["ecdsa-sha2-nistp256"]
        ed25519_schemes = ["ed25519"]
        schemes = [rsa_schemes, ecdsa_schemes, ed25519_schemes]

        for private_key, key_schemes in zip(self.keys, schemes):
            public_key = SSlibKey.from_crypto(private_key.public_key())
            for scheme in key_schemes:
                public_key.scheme = scheme
                signer = CryptoSigner(private_key, public_key)
                sig = signer.sign(b"DATA")
                self.assertIsNone(signer.public_key.verify_signature(sig, b"DATA"))
                with self.assertRaises(UnverifiedSignatureError):
                    signer.public_key.verify_signature(sig, b"NOT DATA")

    def test_from_priv_key_uri(self):
        """Test load and use PEM/PKCS#8 files for each sslib keytype"""
        test_data = [
            (
                "rsa",
                "rsassa-pss-sha256",
                "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhX6rioiL/cX5Ys32InF\nU52H8tL14QeX0tacZdb+AwcH6nIh97h3RSHvGD7Xy6uaMRmGldAnSVYwJHqoJ5j2\nynVzU/RFpr+6n8Ps0QFg5GmlEqZboFjLbS0bsRQcXXnqJNsVLEPT3ULvu1rFRbWz\nAMFjNtNNk5W/u0GEzXn3D03jIdhD8IKAdrTRf0VMD9TRCXLdMmEU2vkf1NVUnOTb\n/dRX5QA8TtBylVnouZknbavQ0J/pPlHLfxUgsKzodwDlJmbPG9BWwXqQCmP0DgOG\nNIZ1X281MOBaGbkNVEuntNjCSaQxQjfALVVU5NAfal2cwMINtqaoc7Wa+TWvpFEI\nWwIDAQAB\n-----END PUBLIC KEY-----\n",
                "rsa_private.pem",
            ),
            (
                "ecdsa",
                "ecdsa-sha2-nistp256",
                "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcLYSZyFGeKdWNt5dWFbnv6N9NyHC\noUNLcG6GZIxLwN8Q8MUdHdOOxGkDnyBRSJpIZ/r/oDECSTwfCYhdogweLA==\n-----END PUBLIC KEY-----\n",
                "ecdsa_private.pem",
            ),
            (
                "ecdsa-sha2-nistp256",  # keytype deprecated in TUF spec, tested for backwards compat with older metadata
                "ecdsa-sha2-nistp256",
                "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcLYSZyFGeKdWNt5dWFbnv6N9NyHC\noUNLcG6GZIxLwN8Q8MUdHdOOxGkDnyBRSJpIZ/r/oDECSTwfCYhdogweLA==\n-----END PUBLIC KEY-----\n",
                "ecdsa_private.pem",
            ),
            (
                "ed25519",
                "ed25519",
                "4f66dabebcf30628963786001984c0b75c175cdcf3bc4855933a2628f0cd0a0f",
                "ed25519_private.pem",
            ),
        ]

        for keytype, scheme, public_key_value, fname in test_data:
            for use_prefix in [True, False]:
                if use_prefix:
                    # uri path is relative from CRYPTO_SIGNER_PATH_PREFIX
                    os.environ["CRYPTO_SIGNER_PATH_PREFIX"] = str(PEMS_DIR)
                    uri = f"file2:{fname}"
                else:
                    with suppress(KeyError):
                        del os.environ["CRYPTO_SIGNER_PATH_PREFIX"]
                    uri = f"file2:{PEMS_DIR / fname}"

                public_key = SSlibKey(
                    "abcdefg", keytype, scheme, {"public": public_key_value}
                )
                signer = Signer.from_priv_key_uri(uri, public_key)
                self.assertIsInstance(signer, CryptoSigner)

                sig = signer.sign(b"DATA")
                self.assertIsNone(signer.public_key.verify_signature(sig, b"DATA"))
                with self.assertRaises(UnverifiedSignatureError):
                    signer.public_key.verify_signature(sig, b"NOT DATA")

        with self.assertRaises(OSError):
            # file not found
            uri = "file2:nonexistentfile"
            Signer.from_priv_key_uri(uri, public_key)

    def test_generate(self):
        """Test generate and use signer (key pair) for each sslib keytype"""
        test_data = [
            (CryptoSigner.generate_rsa, "rsa", "rsassa-pss-sha256"),
            (CryptoSigner.generate_ecdsa, "ecdsa", "ecdsa-sha2-nistp256"),
            (CryptoSigner.generate_ed25519, "ed25519", "ed25519"),
        ]
        for generate, keytype, default_scheme in test_data:
            signer = generate()
            self.assertEqual(signer.public_key.keytype, keytype)
            self.assertEqual(signer.public_key.scheme, default_scheme)

            sig = signer.sign(b"DATA")
            self.assertIsNone(signer.public_key.verify_signature(sig, b"DATA"))
            with self.assertRaises(UnverifiedSignatureError):
                signer.public_key.verify_signature(sig, b"NOT DATA")

    def test_private_bytes(self):
        """Test private_bytes -> from_priv_key_uri"""
        with tempfile.TemporaryDirectory() as tempdir:
            priv_key_path = os.path.join(tempdir, "privkey.pem")
            for pem in ["rsa", "ecdsa", "ed25519"]:
                with open(PEMS_DIR / f"{pem}_private.pem", "rb") as f:
                    privkey = load_pem_private_key(f.read(), None)
                    signer = CryptoSigner(privkey)

                with open(priv_key_path, "wb") as f:
                    f.write(signer.private_bytes)

                signer2 = Signer.from_priv_key_uri(
                    f"file2:{priv_key_path}", signer.public_key
                )
                self.assertEqual(signer.private_bytes, signer2.private_bytes)

    def test_custom_crypto_signer(self):
        # setup
        key = self.keys[0]
        pubkey = SSlibKey.from_crypto(key.public_key())

        class CustomSigner(CryptoSigner):
            """Custom signer with a hard coded key"""

            CUSTOM_SCHEME = "custom"

            @classmethod
            def from_priv_key_uri(
                cls,
                priv_key_uri: str,
                public_key: Key,
                secrets_handler: Optional[SecretsHandler] = None,
            ) -> "CustomSigner":
                return cls(key)

        # register custom signer
        SIGNER_FOR_URI_SCHEME[CustomSigner.CUSTOM_SCHEME] = CustomSigner

        # test signing
        signer = Signer.from_priv_key_uri("custom:foo", pubkey)
        self.assertIsInstance(signer, CustomSigner)
        sig = signer.sign(b"DATA")

        pubkey.verify_signature(sig, b"DATA")
        with self.assertRaises(UnverifiedSignatureError):
            pubkey.verify_signature(sig, b"NOT DATA")


# Run the unit tests.
if __name__ == "__main__":
    unittest.main()
