# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.


import base64
import datetime
import os
import typing

import pytest

from cryptography import utils
from cryptography.exceptions import (
    InvalidSignature,
    InvalidTag,
    UnsupportedAlgorithm,
)
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import (
    dsa,
    ec,
    ed25519,
    rsa,
)
from cryptography.hazmat.primitives.serialization import (
    BestAvailableEncryption,
    Encoding,
    KeySerializationEncryption,
    NoEncryption,
    PrivateFormat,
    PublicFormat,
    SSHCertificate,
    SSHCertificateBuilder,
    SSHCertificateType,
    load_pem_private_key,
    load_ssh_private_key,
    load_ssh_public_identity,
    load_ssh_public_key,
    ssh,
    ssh_key_fingerprint,
)

from ...doubles import DummyKeySerializationEncryption
from ...utils import load_vectors_from_file, raises_unsupported_algorithm
from .fixtures_rsa import RSA_KEY_2048
from .test_ec import _skip_curve_unsupported
from .test_rsa import rsa_key_2048

__all__ = ["rsa_key_2048"]


class TestOpenSSHSerialization:
    @pytest.mark.parametrize(
        ("key_file", "cert_file"),
        [
            ("rsa-psw.key.pub", None),
            ("rsa-nopsw.key.pub", "rsa-nopsw.key-cert.pub"),
            ("dsa-psw.key.pub", None),
            ("dsa-nopsw.key.pub", "dsa-nopsw.key-cert.pub"),
            ("ecdsa-psw.key.pub", None),
            ("ecdsa-nopsw.key.pub", "ecdsa-nopsw.key-cert.pub"),
            ("ed25519-psw.key.pub", None),
            ("ed25519-nopsw.key.pub", "ed25519-nopsw.key-cert.pub"),
            ("sk-ecdsa-psw.key.pub", None),
            ("sk-ecdsa-nopsw.key.pub", None),
            ("sk-ed25519-psw.key.pub", None),
            ("sk-ed25519-nopsw.key.pub", None),
        ],
    )
    def test_load_ssh_public_key(self, key_file, cert_file):
        # normal public key
        pub_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", key_file),
            lambda f: f.read(),
            mode="rb",
        )
        nocomment_data = b" ".join(pub_data.split()[:2])
        if key_file.startswith("dsa"):
            with pytest.warns(utils.DeprecatedIn40):
                public_key = load_ssh_public_key(pub_data)
            with pytest.warns(utils.DeprecatedIn40):
                assert (
                    public_key.public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )
        else:
            public_key = load_ssh_public_key(pub_data)
            if not key_file.startswith("sk-"):
                # SK keys do not round-trip
                assert (
                    public_key.public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )

        self.run_partial_pubkey(pub_data)

        # parse public key with ssh certificate
        if cert_file:
            cert_data = load_vectors_from_file(
                os.path.join("asymmetric", "OpenSSH", cert_file),
                lambda f: f.read(),
                mode="rb",
            )
            if cert_file.startswith("dsa"):
                with pytest.warns(utils.DeprecatedIn40):
                    cert_key = load_ssh_public_key(cert_data)
                with pytest.warns(utils.DeprecatedIn40):
                    assert (
                        cert_key.public_bytes(
                            Encoding.OpenSSH, PublicFormat.OpenSSH
                        )
                        == nocomment_data
                    )
            else:
                cert_key = load_ssh_public_key(cert_data)
                assert (
                    cert_key.public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )

            # try with more spaces
            cert_data = b" \t ".join(cert_data.split())
            if cert_file.startswith("dsa"):
                with pytest.warns(utils.DeprecatedIn40):
                    cert_key = load_ssh_public_key(cert_data)
                with pytest.warns(utils.DeprecatedIn40):
                    assert (
                        cert_key.public_bytes(
                            Encoding.OpenSSH, PublicFormat.OpenSSH
                        )
                        == nocomment_data
                    )
            else:
                cert_key = load_ssh_public_key(cert_data)
                assert (
                    cert_key.public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )

            self.run_partial_pubkey(cert_data)

    def run_partial_pubkey(self, pubdata):
        parts = pubdata.split()
        raw = base64.b64decode(parts[1])
        for i in range(1, len(raw)):
            frag = base64.b64encode(raw[:i])
            new_pub = b" ".join([parts[0], frag])
            with pytest.raises(ValueError):
                load_ssh_public_key(new_pub)

    @pytest.mark.parametrize(
        "key_file",
        [
            "rsa-nopsw.key",
            "rsa-psw.key",
            "dsa-nopsw.key",
            "dsa-psw.key",
            "ecdsa-nopsw.key",
            "ecdsa-psw.key",
            "ed25519-nopsw.key",
            "ed25519-psw.key",
            "ed25519-aesgcm-psw.key",
        ],
    )
    def test_load_ssh_private_key(self, key_file):
        if "-psw" in key_file and not ssh._bcrypt_supported:
            pytest.skip("Requires bcrypt module")

        # read public and private key from ssh-keygen
        priv_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", key_file),
            lambda f: f.read(),
            mode="rb",
        )
        pub_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", key_file + ".pub"),
            lambda f: f.read(),
            mode="rb",
        )
        nocomment_data = b" ".join(pub_data.split()[:2])

        # load and compare
        password = None
        if "-psw" in key_file:
            password = b"password"
        if password is None:
            datas = [
                priv_data,
                bytearray(priv_data),
                memoryview(priv_data),
                memoryview(bytearray(priv_data)),
            ]
        else:
            # Each load of an encrypted key runs the (intentionally slow)
            # bcrypt KDF; bytes-like handling of encrypted keys is covered
            # by test_bcrypt_encryption.
            datas = [priv_data]
        for data in datas:
            if key_file.startswith("dsa"):
                with pytest.warns(utils.DeprecatedIn40):
                    private_key = load_ssh_private_key(data, password)
                with pytest.warns(utils.DeprecatedIn40):
                    assert (
                        private_key.public_key().public_bytes(
                            Encoding.OpenSSH, PublicFormat.OpenSSH
                        )
                        == nocomment_data
                    )
            else:
                private_key = load_ssh_private_key(data, password)
                assert (
                    private_key.public_key().public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )

        # serialize with own code and reload
        encryption: KeySerializationEncryption = NoEncryption()
        if password:
            encryption = BestAvailableEncryption(password)
        if key_file.startswith("dsa"):
            with pytest.warns(utils.DeprecatedIn40):
                priv_data2 = private_key.private_bytes(
                    Encoding.PEM,
                    PrivateFormat.OpenSSH,
                    encryption,
                )
            with pytest.warns(utils.DeprecatedIn40):
                private_key2 = load_ssh_private_key(priv_data2, password)
            with pytest.warns(utils.DeprecatedIn40):
                assert (
                    private_key2.public_key().public_bytes(
                        Encoding.OpenSSH, PublicFormat.OpenSSH
                    )
                    == nocomment_data
                )
        else:
            priv_data2 = private_key.private_bytes(
                Encoding.PEM,
                PrivateFormat.OpenSSH,
                encryption,
            )
            private_key2 = load_ssh_private_key(priv_data2, password)
            assert (
                private_key2.public_key().public_bytes(
                    Encoding.OpenSSH, PublicFormat.OpenSSH
                )
                == nocomment_data
            )

        # make sure multi-line base64 is used
        maxline = max(map(len, priv_data2.split(b"\n")))
        assert maxline < 80

    @pytest.mark.parametrize(
        "key_file",
        [
            "sk-ecdsa-nopsw.key",
            "sk-ed25519-nopsw.key",
        ],
    )
    def test_load_unsupported_ssh_private_key(self, key_file):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", key_file),
            lambda f: f.read(),
            mode="rb",
        )
        with pytest.raises(UnsupportedAlgorithm):
            load_ssh_private_key(data, None)

    @pytest.mark.supported(
        only_if=lambda backend: ssh._bcrypt_supported,
        skip_message="Requires that bcrypt exists",
    )
    def test_load_ssh_private_key_invalid_tag(self):
        priv_data = bytearray(
            load_vectors_from_file(
                os.path.join(
                    "asymmetric", "OpenSSH", "ed25519-aesgcm-psw.key"
                ),
                lambda f: f.read(),
                mode="rb",
            )
        )
        # mutate one byte to break the tag
        priv_data[-38] = 82
        with pytest.raises(InvalidTag):
            load_ssh_private_key(priv_data, b"password")

    @pytest.mark.supported(
        only_if=lambda backend: ssh._bcrypt_supported,
        skip_message="Requires that bcrypt exists",
    )
    def test_load_ssh_private_key_tag_incorrect_length(self):
        priv_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ed25519-aesgcm-psw.key"),
            lambda f: f.read(),
            mode="rb",
        )
        # clip out a byte
        broken_data = priv_data[:-37] + priv_data[-38:]
        with pytest.raises(ValueError):
            load_ssh_private_key(broken_data, b"password")

    @pytest.mark.supported(
        only_if=lambda backend: ssh._bcrypt_supported,
        skip_message="Requires that bcrypt exists",
    )
    def test_bcrypt_encryption(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        pub1 = private_key.public_key().public_bytes(
            Encoding.OpenSSH, PublicFormat.OpenSSH
        )

        for psw in (
            b"1",
            b"1234",
            b"1234" * 4,
            b"x" * 72,
        ):
            # Minimal KDF rounds: this test is about password and
            # bytes-like handling, not KDF strength.
            encryption = (
                PrivateFormat.OpenSSH.encryption_builder()
                .kdf_rounds(1)
                .build(psw)
            )
            encdata = private_key.private_bytes(
                Encoding.PEM, PrivateFormat.OpenSSH, encryption
            )
            decoded_key = load_ssh_private_key(encdata, psw)
            pub2 = decoded_key.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )
            assert pub1 == pub2

            # bytearray
            decoded_key2 = load_ssh_private_key(bytearray(encdata), psw)
            pub2 = decoded_key2.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )
            assert pub1 == pub2

            # memoryview(bytes)
            decoded_key2 = load_ssh_private_key(memoryview(encdata), psw)
            pub2 = decoded_key2.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )
            assert pub1 == pub2

            # memoryview(bytearray)
            decoded_key2 = load_ssh_private_key(
                memoryview(bytearray(encdata)), psw
            )
            pub2 = decoded_key2.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )
            assert pub1 == pub2

            with pytest.raises(TypeError):
                decoded_key = load_ssh_private_key(encdata, None)
            with pytest.raises(ValueError):
                decoded_key = load_ssh_private_key(encdata, b"wrong")

    @pytest.mark.supported(
        only_if=lambda backend: not ssh._bcrypt_supported,
        skip_message="Requires that bcrypt is missing",
    )
    def test_missing_bcrypt(self):
        priv_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ecdsa-psw.key"),
            lambda f: f.read(),
            mode="rb",
        )
        with raises_unsupported_algorithm(None):
            load_ssh_private_key(priv_data, b"password")

        private_key = ec.generate_private_key(ec.SECP256R1())
        with raises_unsupported_algorithm(None):
            private_key.private_bytes(
                Encoding.PEM,
                PrivateFormat.OpenSSH,
                BestAvailableEncryption(b"x"),
            )

    def test_fraglist_corners(self):
        f = ssh._FragList()
        with pytest.raises(ValueError):
            f.put_mpint(-1)
        f.put_mpint(0)
        f.put_mpint(0x80)
        assert f.tobytes() == b"\0\0\0\0" + b"\0\0\0\x02" + b"\0\x80"

    def make_file(
        self,
        magic=b"openssh-key-v1\0",
        ciphername=b"none",
        kdfname=b"none",
        kdfoptions=b"",
        nkeys=1,
        pub_type=b"ecdsa-sha2-nistp256",
        pub_fields=(
            b"nistp256",
            b"\x04" * 65,
        ),
        priv_type=None,
        priv_fields=(b"nistp256", b"\x04" * 65, b"\x7f" * 32),
        comment=b"comment",
        checkval1=b"1234",
        checkval2=b"1234",
        pad=None,
        header=b"-----BEGIN OPENSSH PRIVATE KEY-----\n",
        footer=b"-----END OPENSSH PRIVATE KEY-----\n",
        cut=8192,
    ):
        """Create private key file"""
        if not priv_type:
            priv_type = pub_type

        pub = ssh._FragList()
        for elem in (pub_type, *pub_fields):
            pub.put_sshstr(elem)

        secret = ssh._FragList([checkval1, checkval2])
        for i in range(nkeys):
            for elem in (priv_type, *priv_fields, comment):
                secret.put_sshstr(elem)

        if pad is None:
            pad_len = 8 - (secret.size() % 8)
            pad = bytearray(range(1, 1 + pad_len))
        secret.put_raw(pad)

        main = ssh._FragList([magic])
        main.put_sshstr(ciphername)
        main.put_sshstr(kdfname)
        main.put_sshstr(kdfoptions)
        main.put_u32(nkeys)
        for i in range(nkeys):
            main.put_sshstr(pub)
        main.put_sshstr(secret)

        res = main.tobytes()
        return header + base64.encodebytes(res[:cut]) + footer

    def test_ssh_make_file(self):
        # check if works by default
        data = self.make_file()
        key = load_ssh_private_key(data, None)
        assert isinstance(key, ec.EllipticCurvePrivateKey)

    def test_load_ssh_private_key_errors(self):
        # bad kdf
        data = self.make_file(kdfname=b"unknown", ciphername=b"aes256-ctr")
        with raises_unsupported_algorithm(None):
            load_ssh_private_key(data, None)

        # bad cipher
        data = self.make_file(ciphername=b"unknown", kdfname=b"bcrypt")
        with raises_unsupported_algorithm(None):
            load_ssh_private_key(data, None)

        # bad magic
        data = self.make_file(magic=b"unknown")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # too few keys
        data = self.make_file(nkeys=0)
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # too many keys
        data = self.make_file(nkeys=2)
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_errors_bad_values(self):
        # bad curve
        data = self.make_file(pub_type=b"ecdsa-sha2-nistp444")
        with raises_unsupported_algorithm(None):
            load_ssh_private_key(data, None)

        # curve mismatch
        data = self.make_file(priv_type=b"ecdsa-sha2-nistp384")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # invalid bigint
        data = self.make_file(
            priv_fields=(b"nistp256", b"\x04" * 65, b"\x80" * 32)
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_errors_pubpriv_mismatch(self):
        # ecdsa public-private mismatch
        data = self.make_file(
            pub_fields=(
                b"nistp256",
                b"\x04" + b"\x05" * 64,
            )
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # rsa public-private mismatch
        data = self.make_file(
            pub_type=b"ssh-rsa",
            pub_fields=(b"x" * 32,) * 2,
            priv_fields=(b"z" * 32,) * 6,
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # dsa public-private mismatch
        data = self.make_file(
            pub_type=b"ssh-dss",
            pub_fields=(b"x" * 32,) * 4,
            priv_fields=(b"z" * 32,) * 5,
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # ed25519 public-private mismatch
        sk = b"x" * 32
        pk1 = b"y" * 32
        pk2 = b"z" * 32
        data = self.make_file(
            pub_type=b"ssh-ed25519",
            pub_fields=(pk1,),
            priv_fields=(
                pk1,
                sk + pk2,
            ),
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)
        data = self.make_file(
            pub_type=b"ssh-ed25519",
            pub_fields=(pk1,),
            priv_fields=(
                pk2,
                sk + pk1,
            ),
        )
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_errors_bad_wrapper(self):
        # wrong header
        data = self.make_file(header=b"-----BEGIN RSA PRIVATE KEY-----\n")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # wring footer
        data = self.make_file(footer=b"-----END RSA PRIVATE KEY-----\n")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_no_padding(self):
        # no padding must work, if data is on block boundary
        data = self.make_file(pad=b"", comment=b"")
        key = load_ssh_private_key(data, None)
        assert isinstance(key, ec.EllipticCurvePrivateKey)

        # no padding with right last byte
        data = self.make_file(pad=b"", comment=b"\x08" * 8)
        key = load_ssh_private_key(data, None)
        assert isinstance(key, ec.EllipticCurvePrivateKey)

        # avoid unexpected padding removal
        data = self.make_file(pad=b"", comment=b"1234\x01\x02\x03\x04")
        key = load_ssh_private_key(data, None)
        assert isinstance(key, ec.EllipticCurvePrivateKey)

        # bad padding with right size
        data = self.make_file(pad=b"\x08" * 8, comment=b"")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_errors_bad_secrets(self):
        # checkval mismatch
        data = self.make_file(checkval2=b"4321")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

        # bad padding, correct=1
        data = self.make_file(pad=b"\x01\x02")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)
        data = self.make_file(pad=b"")
        with pytest.raises(ValueError):
            load_ssh_private_key(data, None)

    def test_ssh_errors_unencrypted_with_password(self):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key"),
            lambda f: f.read(),
            mode="rb",
        )
        with pytest.raises(TypeError):
            load_ssh_private_key(data, password=b"password")

    @pytest.mark.supported(
        only_if=lambda backend: backend.elliptic_curve_supported(
            ec.SECP192R1()
        ),
        skip_message="Requires backend support for ec.SECP192R1",
    )
    def test_serialize_ssh_private_key_errors_bad_curve(self):
        private_key = ec.generate_private_key(ec.SECP192R1())
        with pytest.raises(ValueError):
            private_key.private_bytes(
                Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption()
            )

    def test_serialize_ssh_private_key_errors(
        self, rsa_key_2048: rsa.RSAPrivateKey
    ):
        # bad encoding
        private_key = ec.generate_private_key(ec.SECP256R1())
        with pytest.raises(ValueError):
            private_key.private_bytes(
                Encoding.DER, PrivateFormat.OpenSSH, NoEncryption()
            )

        # bad object type
        with pytest.raises(ValueError):
            ssh._serialize_ssh_private_key(
                typing.cast(typing.Any, object()),
                b"",
                NoEncryption(),
            )

        private_key = ec.generate_private_key(ec.SECP256R1())

        # unknown encryption class
        with pytest.raises(ValueError):
            private_key.private_bytes(
                Encoding.PEM,
                PrivateFormat.OpenSSH,
                DummyKeySerializationEncryption(),
            )

        with pytest.raises(ValueError):
            rsa_key_2048.private_bytes(
                Encoding.DER, PrivateFormat.OpenSSH, NoEncryption()
            )

    @pytest.mark.supported(
        only_if=lambda backend: ssh._bcrypt_supported,
        skip_message="Requires that bcrypt exists",
    )
    @pytest.mark.parametrize(
        "password",
        (
            b"1234",
            b"x" * 100,
        ),
    )
    @pytest.mark.parametrize(
        "kdf_rounds",
        [
            1,
            10,
            # Greater than the default of 16
            18,
        ],
    )
    def test_serialize_ssh_private_key_with_password(
        self, password, kdf_rounds, rsa_key_2048: rsa.RSAPrivateKey
    ):
        for original_key in [
            ec.generate_private_key(ec.SECP256R1()),
            rsa_key_2048,
        ]:
            assert isinstance(
                original_key, (ec.EllipticCurvePrivateKey, rsa.RSAPrivateKey)
            )
            encoded_key_data = original_key.private_bytes(
                Encoding.PEM,
                PrivateFormat.OpenSSH,
                (
                    PrivateFormat.OpenSSH.encryption_builder()
                    .kdf_rounds(kdf_rounds)
                    .build(password)
                ),
            )

            decoded_key = load_ssh_private_key(
                data=encoded_key_data, password=password
            )

            original_public_key = original_key.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )

            decoded_public_key = decoded_key.public_key().public_bytes(
                Encoding.OpenSSH, PublicFormat.OpenSSH
            )

            assert original_public_key == decoded_public_key

    @pytest.mark.supported(
        only_if=lambda backend: backend.dsa_supported(),
        skip_message="Does not support DSA.",
    )
    @pytest.mark.parametrize(
        ("key_path", "supported"),
        [
            (["Traditional_OpenSSL_Serialization", "dsa.1024.pem"], True),
            (["Traditional_OpenSSL_Serialization", "dsa.2048.pem"], False),
            (["Traditional_OpenSSL_Serialization", "dsa.3072.pem"], False),
        ],
    )
    def test_dsa_private_key_sizes(self, key_path, supported):
        key = load_vectors_from_file(
            os.path.join("asymmetric", *key_path),
            lambda pemfile: load_pem_private_key(pemfile.read(), None),
            mode="rb",
        )
        assert isinstance(key, dsa.DSAPrivateKey)
        if supported:
            with pytest.warns(utils.DeprecatedIn40):
                res = key.private_bytes(
                    Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption()
                )
            assert isinstance(res, bytes)
        else:
            with pytest.raises(ValueError):
                with pytest.warns(utils.DeprecatedIn40):
                    key.private_bytes(
                        Encoding.PEM, PrivateFormat.OpenSSH, NoEncryption()
                    )


class TestRSASSHSerialization:
    def test_load_ssh_public_key_unsupported(self):
        ssh_key = b"ecdsa-sha2-junk AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY="

        with raises_unsupported_algorithm(None):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_bad_format(self):
        ssh_key = b"ssh-rsa not-a-real-key"

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_rsa_too_short(self):
        ssh_key = b"ssh-rsa"

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_truncated_int(self):
        ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAA="

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

        ssh_key = b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo"

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_rsa_comment_with_spaces(self):
        ssh_key = (
            b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk"
            b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll"
            b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK"
            b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f"
            b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy"
            b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX"
            # Extra section appended
            b"2MzHvnbv testkey@localhost extra"
        )

        load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_rsa_extra_data_after_modulo(self):
        ssh_key = (
            b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk"
            b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll"
            b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK"
            b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f"
            b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy"
            b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX"
            b"2MzHvnbvAQ== testkey@localhost"
        )

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_rsa_different_string(self):
        ssh_key = (
            # "AAAAB3NzA" the final A is capitalized here to cause the string
            # ssh-rsa inside the base64 encoded blob to be incorrect. It should
            # be a lower case 'a'.
            b"ssh-rsa AAAAB3NzAC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk"
            b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll"
            b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK"
            b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f"
            b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy"
            b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX"
            b"2MzHvnbvAQ== testkey@localhost"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_rsa(self):
        ssh_key = (
            b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk"
            b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll"
            b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK"
            b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f"
            b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy"
            b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX"
            b"2MzHvnbv testkey@localhost"
        )

        key = load_ssh_public_key(ssh_key)

        assert key is not None
        assert isinstance(key, rsa.RSAPublicKey)

        numbers = key.public_numbers()

        expected_e = 0x10001
        expected_n = int(
            "00C3BBF5D13F59322BA0A0B77EA0B6CF570241628AE24B5BA454D"
            "23DCA295652B3523B67752653DFFD69587FAD9578DD6406F23691"
            "EA491C3F8B2D391D0312D9653C303B651067ADF887A5241843CEF"
            "8019680A088E092FEC305FB04EA070340BB9BD0F1635B2AD84142"
            "61B4E2D010ABD8FC6D2FB768912F78EE6B05A60857532B75B75EF"
            "C007601A4EF58BA947B7E75E38F3443CDD87E7C138A1DAD9D9FB3"
            "19FF69DA43A9F6F6B0CD243F042CD1A5AFAEB286BD46AEB2D922B"
            "D01385D6892167074A0907F94A2BF08A54ABB2FFFFC89920861D0"
            "46F8706AB88DDADBD9E8204D48B87789081E074024C8996783B31"
            "7076A98ABF0A2D8550EAF2097D8CCC7BE76EF",
            16,
        )

        expected = rsa.RSAPublicNumbers(expected_e, expected_n)

        assert numbers == expected

    def test_unsafe_skip_rsa_key_validation(self):
        key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key"),
            lambda f: load_ssh_private_key(
                f.read(), password=None, unsafe_skip_rsa_key_validation=True
            ),
            mode="rb",
        )
        assert isinstance(key, rsa.RSAPrivateKey)


class TestDSSSSHSerialization:
    def test_load_ssh_public_key_dss_too_short(self):
        ssh_key = b"ssh-dss"

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_dss_comment_with_spaces(self):
        ssh_key = (
            b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD"
            b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm"
            b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ"
            b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2"
            b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf"
            b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J"
            b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi"
            b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa"
            b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr"
            b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra"
        )

        with pytest.warns(utils.DeprecatedIn40):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_dss_extra_data_after_modulo(self):
        ssh_key = (
            b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD"
            b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm"
            b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ"
            b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2"
            b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf"
            b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J"
            b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi"
            b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa"
            b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr"
            b"z53N7tPF/IhHTjBHb1Ol7IFu9p9AAwMD== testkey@localhost"
        )

        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_dss_different_string(self):
        ssh_key = (
            # "AAAAB3NzA" the final A is capitalized here to cause the string
            # ssh-dss inside the base64 encoded blob to be incorrect. It should
            # be a lower case 'a'.
            b"ssh-dss AAAAB3NzAC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD"
            b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm"
            b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ"
            b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2"
            b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf"
            b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J"
            b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi"
            b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa"
            b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr"
            b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_dss(self):
        ssh_key = (
            b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD"
            b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm"
            b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ"
            b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2"
            b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf"
            b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J"
            b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi"
            b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa"
            b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr"
            b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost"
        )

        with pytest.warns(utils.DeprecatedIn40):
            key = load_ssh_public_key(ssh_key)

        assert key is not None
        assert isinstance(key, dsa.DSAPublicKey)

        numbers = key.public_numbers()

        expected_y = int(
            "d143cf92901f936fa258e9a11422460c8f8f1597884eef8cb1252a3e2ff0aae"
            "96a7032c01cdd8485b5cbfb73a46bb04708f98a18bc88d4c7812b284da8f900"
            "6e473e89897f9bc9125c69bbfd8ef691c0e76c1c34e6c843b8fe240e6e5aeb3"
            "13486e5fa917ab1288ff1a6ebcf9dcdeed3c5fc88474e30476f53a5ec816ef6"
            "9f4",
            16,
        )
        expected_p = int(
            "b9b052d7f07630148d4d838b17790ef4f43437238ebebd5032ea483fd7b7902"
            "5ec3dc65ebd563ab586a633b4344f6acd10af31353bcf29111fa5e3b8d5c1e8"
            "7befe3c65f9b8be69c740716698c8366c8ef925b9cec1dcd69e73d926b554e2"
            "b4b6ddd1453eab39ba0f846e1555adcc33c5a8637128c9ed61104a45505a748"
            "f6db",
            16,
        )
        expected_q = 1230879958723280233885494314531920096931919647917
        expected_g = int(
            "7f6c9170b2cfb67e78267c6fcb8b93b22fb03d895a0676451a15ac44511393a"
            "7bc249b6cf8f5f5c5022afefd4df5bf9d13bbdf182df5af2a5c5d1dc7604185"
            "7d5b0e4b22b856c300f850a3b00bac394b728755b8b7a56522eefc491573967"
            "debb5982fc94d6a8c291f758feae63ad769a5621947221522a2dc31d18ede6f"
            "b656",
            16,
        )
        expected = dsa.DSAPublicNumbers(
            expected_y,
            dsa.DSAParameterNumbers(expected_p, expected_q, expected_g),
        )

        assert numbers == expected


class TestECDSASSHSerialization:
    def test_load_ssh_public_key_ecdsa_nist_p256(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())

        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01"
        )
        key = load_ssh_public_key(ssh_key, backend)
        assert isinstance(key, ec.EllipticCurvePublicKey)

        expected_x = int(
            "44196257377740326295529888716212621920056478823906609851236662550"
            "785814128027",
            10,
        )
        expected_y = int(
            "12257763433170736656417248739355923610241609728032203358057767672"
            "925775019611",
            10,
        )

        assert key.public_numbers() == ec.EllipticCurvePublicNumbers(
            expected_x, expected_y, ec.SECP256R1()
        )

    def test_load_ssh_public_key_byteslike(self, backend):
        _skip_curve_unsupported(backend, ec.SECP256R1())

        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01"
        )
        assert load_ssh_public_key(bytearray(ssh_key), backend)
        assert load_ssh_public_key(memoryview(ssh_key), backend)
        assert load_ssh_public_key(memoryview(bytearray(ssh_key)), backend)

    def test_load_ssh_public_key_ecdsa_nist_p384(self, backend):
        _skip_curve_unsupported(backend, ec.SECP384R1())
        ssh_key = (
            b"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAz"
            b"ODQAAABhBMzucOm9wbwg4iMr5QL0ya0XNQGXpw4wM5f12E3tWhdcrzyGHyel71t1"
            b"4bvF9JZ2/WIuSxUr33XDl8jYo+lMQ5N7Vanc7f7i3AR1YydatL3wQfZStQ1I3rBa"
            b"qQtRSEU8Tg== root@cloud-server-01"
        )
        key = load_ssh_public_key(ssh_key, backend)
        assert isinstance(key, ec.EllipticCurvePublicKey)

        expected_x = int(
            "31541830871345183397582554827482786756220448716666815789487537666"
            "592636882822352575507883817901562613492450642523901",
            10,
        )
        expected_y = int(
            "15111413269431823234030344298767984698884955023183354737123929430"
            "995703524272335782455051101616329050844273733614670",
            10,
        )

        assert key.public_numbers() == ec.EllipticCurvePublicNumbers(
            expected_x, expected_y, ec.SECP384R1()
        )

    def test_load_ssh_public_key_ecdsa_nist_p521(self, backend):
        _skip_curve_unsupported(backend, ec.SECP521R1())
        ssh_key = (
            b"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1"
            b"MjEAAACFBAGTrRhMSEgF6Ni+PXNz+5fjS4lw3ypUILVVQ0Av+0hQxOx+MyozELon"
            b"I8NKbrbBjijEs1GuImsmkTmWsMXS1j2A7wB4Kseh7W9KA9IZJ1+TMrzWUEwvOOXi"
            b"wT23pbaWWXG4NaM7vssWfZBnvz3S174TCXnJ+DSccvWBFnKP0KchzLKxbg== "
            b"root@cloud-server-01"
        )
        key = load_ssh_public_key(ssh_key, backend)
        assert isinstance(key, ec.EllipticCurvePublicKey)

        expected_x = int(
            "54124123120178189598842622575230904027376313369742467279346415219"
            "77809037378785192537810367028427387173980786968395921877911964629"
            "142163122798974160187785455",
            10,
        )
        expected_y = int(
            "16111775122845033200938694062381820957441843014849125660011303579"
            "15284560361402515564433711416776946492019498546572162801954089916"
            "006665939539407104638103918",
            10,
        )

        assert key.public_numbers() == ec.EllipticCurvePublicNumbers(
            expected_x, expected_y, ec.SECP521R1()
        )

    def test_load_ssh_public_key_ecdsa_nist_p256_trailing_data(self):
        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCFPltB= root@cloud-server-01"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_ecdsa_nist_p256_missing_data(self):
        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCF= root@cloud-server-01"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_ecdsa_nist_p256_compressed(self):
        # If we ever implement compressed points, note that this is not a valid
        # one, it just has the compressed marker in the right place.
        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAABBAWG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01"
        )
        with pytest.raises(NotImplementedError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_ecdsa_nist_p256_empty_point(self):
        # Malformed key with empty point data should raise ValueError,
        # not IndexError.
        ssh_key = (
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTYAAAAA"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_ecdsa_nist_p256_bad_curve_name(self):
        ssh_key = (
            # The curve name in here is changed to be "nistp255".
            b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy"
            b"NTUAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5"
            b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)


class TestEd25519SSHSerialization:
    def test_load_ssh_public_key(self):
        ssh_key = (
            b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4"
            b"GR+xWvBmvxjxrB1vG user@chiron.local"
        )
        key = load_ssh_public_key(ssh_key)
        assert isinstance(key, ed25519.Ed25519PublicKey)
        assert key.public_bytes(Encoding.Raw, PublicFormat.Raw) == (
            b"m\x9f\x82\x99\xa9`\xee\xb5\xa9\xe01\x19\xdd0\x81\x16\x8d\xfc"
            b"N\x06G\xecV\xbc\x19\xaf\xc6<k\x07[\xc6"
        )

    def test_public_bytes_openssh(self):
        ssh_key = (
            b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRaN/E4"
            b"GR+xWvBmvxjxrB1vG"
        )
        key = load_ssh_public_key(ssh_key)
        assert isinstance(key, ed25519.Ed25519PublicKey)
        assert (
            key.public_bytes(Encoding.OpenSSH, PublicFormat.OpenSSH) == ssh_key
        )

    def test_load_ssh_public_key_not_32_bytes(self):
        ssh_key = (
            b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI22fgpmpYO61qeAxGd0wgRaN/E4"
            b"GR+xWvBmvxjxrB1vGaGVs user@chiron.local"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)

    def test_load_ssh_public_key_trailing_data(self):
        ssh_key = (
            b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG2fgpmpYO61qeAxGd0wgRa"
            b"N/E4GR+xWvBmvxjxrB1vGdHJhaWxpbmdkYXRh user@chiron.local"
        )
        with pytest.raises(ValueError):
            load_ssh_public_key(ssh_key)


class TestSSHCertificate:
    def test_loads_ssh_cert(self):
        # secp256r1 public key, ed25519 signing key
        cert = load_ssh_public_identity(
            b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbm"
            b"lzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLfsFv9Gbc6LZSiJFWdYQl"
            b"IMNI50GExXW0fBpgGVf+Y4AAAAIbmlzdHAyNTYAAABBBIzVyRgVLR4F38bIOLBN"
            b"8CNm8Nf+eBHCVkKDKb9WDyLLD61CEmzjK/ORwFuSE4N60eIGbFidBf0D0xh7G6o"
            b"TNxsAAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHkuaW8AAAAaAAAACm"
            b"NyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7KyZAAAAAB2frXAAAAAWAAAAA1mb"
            b"3JjZS1jb21tYW5kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh"
            b"YWFhYWFhYWFhYWFhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAACCAAAAFXBlcm1"
            b"pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbm"
            b"cAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wd"
            b"HkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1"
            b"NTE5AAAAICH6csEOmGbOfT2B/S/FJg3uyPsaPSZUZk2SVYlfs0KLAAAAUwAAAAt"
            b"zc2gtZWQyNTUxOQAAAEDz2u7X5/TFbN7Ms7DP4yArhz1oWWYKkdAk7FGFkHfjtY"
            b"/YfNQ8Oky3dCZRi7PnSzScEEjos7723dhF8/y99WwH reaperhulk@despoina."
            b"local"
        )
        assert isinstance(cert, SSHCertificate)
        cert.verify_cert_signature()
        signature_key = cert.signature_key()
        assert isinstance(signature_key, ed25519.Ed25519PublicKey)
        assert cert.nonce == (
            b'-\xfb\x05\xbf\xd1\x9bs\xa2\xd9J"EY\xd6\x10\x94\x83\r#\x9d'
            b"\x06\x13\x15\xd6\xd1\xf0i\x80e_\xf9\x8e"
        )
        public_key = cert.public_key()
        assert isinstance(public_key, ec.EllipticCurvePublicKey)
        assert isinstance(public_key.curve, ec.SECP256R1)
        assert cert.serial == 0
        assert cert.type is SSHCertificateType.USER
        assert cert.key_id == b"test@cryptography.io"
        assert cert.valid_principals == [b"cryptouser", b"testuser"]
        assert cert.valid_before == 1988015552
        assert cert.valid_after == 1672655460
        assert cert.critical_options == {
            b"force-command": b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            b"verify-required": b"",
        }
        assert cert.extensions == {
            b"permit-X11-forwarding": b"",
            b"permit-agent-forwarding": b"",
            b"permit-port-forwarding": b"",
            b"permit-pty": b"",
            b"permit-user-rc": b"",
        }

    @pytest.mark.parametrize(
        "filename",
        [
            "p256-p384.pub",
            "p256-p521.pub",
            "p256-rsa-sha1.pub",
            "p256-rsa-sha256.pub",
            "p256-rsa-sha512.pub",
        ],
    )
    def test_verify_cert_signature(self, filename, backend):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "certs", filename),
            lambda f: f.read(),
            mode="rb",
        )
        cert = load_ssh_public_identity(data)
        # we have no public API for getting the hash alg of the sig
        assert isinstance(cert, SSHCertificate)
        if backend._fips_enabled and bytes(cert._inner_sig_type) == b"ssh-rsa":
            pytest.skip("FIPS does not support RSA SHA1")
        cert.verify_cert_signature()

    @pytest.mark.parametrize(
        "filename",
        [
            "p256-p256-empty-principals.pub",
            "p256-p384.pub",
            "p256-p521.pub",
            "p256-rsa-sha1.pub",
            "p256-rsa-sha256.pub",
            "p256-rsa-sha512.pub",
        ],
    )
    def test_invalid_signature(self, filename, backend):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "certs", filename),
            lambda f: f.read(),
            mode="rb",
        )
        data = bytearray(data)
        # mutate the signature so it's invalid
        data[-10] = 71
        cert = load_ssh_public_identity(data)
        assert isinstance(cert, SSHCertificate)
        # we have no public API for getting the hash alg of the sig
        if backend._fips_enabled and bytes(cert._inner_sig_type) == b"ssh-rsa":
            pytest.skip("FIPS does not support RSA SHA1")
        with pytest.raises(InvalidSignature):
            cert.verify_cert_signature()

    def test_not_bytes(self):
        with pytest.raises(TypeError):
            load_ssh_public_identity(
                typing.cast(typing.Any, "these aren't bytes")
            )

    def test_load_ssh_public_key(self):
        # This test will be removed when we implement load_ssh_public_key
        # in terms of load_ssh_public_identity. Needed for coverage now.
        pub_data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        key = load_ssh_public_identity(pub_data)
        assert isinstance(key, rsa.RSAPublicKey)

    @pytest.mark.parametrize("filename", ["dsa-p256.pub", "p256-dsa.pub"])
    def test_dsa_unsupported(self, filename):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "certs", filename),
            lambda f: f.read(),
            mode="rb",
        )
        with raises_unsupported_algorithm(None):
            load_ssh_public_identity(data)

    def test_mismatched_inner_signature_type_and_sig_type(self):
        data = load_vectors_from_file(
            os.path.join(
                "asymmetric",
                "OpenSSH",
                "certs",
                "p256-p256-broken-signature-key-type.pub",
            ),
            lambda f: f.read(),
            mode="rb",
        )
        with pytest.raises(ValueError):
            load_ssh_public_identity(data)

    def test_invalid_cert_type(self):
        data = load_vectors_from_file(
            os.path.join(
                "asymmetric",
                "OpenSSH",
                "certs",
                "p256-p256-invalid-cert-type.pub",
            ),
            lambda f: f.read(),
            mode="rb",
        )
        with pytest.raises(ValueError):
            load_ssh_public_identity(data)

    @pytest.mark.parametrize(
        "filename",
        [
            "p256-p256-duplicate-extension.pub",
            "p256-p256-non-lexical-extensions.pub",
            "p256-p256-duplicate-crit-opts.pub",
            "p256-p256-non-lexical-crit-opts.pub",
            "p256-ed25519-non-singular-crit-opt-val.pub",
            "p256-ed25519-non-singular-ext-val.pub",
        ],
    )
    def test_invalid_encodings(self, filename):
        data = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "certs", filename),
            lambda f: f.read(),
            mode="rb",
        )
        with pytest.raises(ValueError):
            load_ssh_public_identity(data)

    def test_invalid_line_format(self):
        with pytest.raises(ValueError):
            load_ssh_public_identity(b"whaaaaaaaaaaat")

    def test_invalid_b64(self):
        with pytest.raises(ValueError):
            load_ssh_public_identity(b"ssh-rsa-cert-v01@openssh.com invalid")

    def test_inner_outer_key_type_mismatch(self):
        with pytest.raises(ValueError):
            load_ssh_public_identity(
                b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAK0VjZHNhLXNoYTI"
                b"tbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg/9dq+iibMSMdJ0v"
                b"l6D0SrsazwccWptLQs4sEgJBVnQMAAAAIbmlzdHAyNTYAAABBBAsuVFNNj/m"
                b"MyFm2xB99G4xiaUJE1lZNjcp+S2tXYW5KorcHpusSlSqOkUPZ2l0644dgiNP"
                b"DKR/R+BtYENC8aq8AAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHk"
                b"uaW8AAAAaAAAACmNyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7ZXNAAAAAB"
                b"2glqqAAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABd"
                b"wZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9"
                b"yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI"
                b"tcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1uaXN0cDI1NgAAAAhuaXN"
                b"0cDI1NgAAAEEEzwNcwptXrrgztCug8ZB82f5OsPWJiO4WP0kjdFz1vbBGQOU"
                b"DcCaabh5EbgfMOf1mg58zw35QrqjTXDiBMjyPhwAAAGQAAAATZWNkc2Etc2h"
                b"hMi1uaXN0cDI1NgAAAEkAAAAhAOaNCEtn0JkFfSygACVZMBUMd5/m7avwqxW"
                b"+FxCje1GpAAAAIGf9opl4YoC5XcO92WMFEwUdE3jUQtBg3GRQlXBqFcoL"
            )

    def test_loads_a_cert_empty_principals(self):
        data = load_vectors_from_file(
            os.path.join(
                "asymmetric",
                "OpenSSH",
                "certs",
                "p256-p256-empty-principals.pub",
            ),
            lambda f: f.read(),
            mode="rb",
        )
        cert = load_ssh_public_identity(data)
        assert isinstance(cert, SSHCertificate)
        assert cert.valid_principals == []
        assert cert.extensions == {}
        assert cert.critical_options == {}

    def test_public_bytes(self):
        data = load_vectors_from_file(
            os.path.join(
                "asymmetric",
                "OpenSSH",
                "certs",
                "p256-p256-empty-principals.pub",
            ),
            lambda f: f.read(),
            mode="rb",
        )
        cert = load_ssh_public_identity(data)
        assert isinstance(cert, SSHCertificate)
        assert data == cert.public_bytes()


class TestSSHCertificateBuilder:
    def test_signs_a_cert(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        public_key = ec.generate_private_key(ec.SECP256R1()).public_key()
        valid_after = datetime.datetime(
            2023, 1, 1, 1, tzinfo=datetime.timezone.utc
        ).timestamp()
        valid_before = datetime.datetime(
            2023, 6, 1, 1, tzinfo=datetime.timezone.utc
        ).timestamp()
        key_id = b"test"
        valid_principals = [b"eve", b"alice"]
        builder = (
            SSHCertificateBuilder()
            .public_key(public_key)
            .type(SSHCertificateType.USER)
            .valid_before(valid_before)
            .valid_after(valid_after)
            .key_id(key_id)
            .valid_principals(valid_principals)
            .add_critical_option(b"ordered", b"")
            .add_critical_option(b"maybe", b"test2")
            .add_extension(b"test", b"a value")
            .add_extension(b"allowed", b"")
        )
        cert = builder.sign(private_key)
        cert.verify_cert_signature()
        cert_public_key = cert.public_key()
        assert isinstance(cert_public_key, ec.EllipticCurvePublicKey)
        assert cert_public_key.public_numbers() == public_key.public_numbers()
        assert cert.serial == 0
        assert cert.type is SSHCertificateType.USER
        assert cert.key_id == key_id
        assert cert.valid_principals == valid_principals
        assert cert.valid_before == int(valid_before)
        assert cert.valid_after == int(valid_after)
        assert cert.critical_options == {b"ordered": b"", b"maybe": b"test2"}
        assert list(cert.critical_options) == [b"maybe", b"ordered"]
        assert cert.extensions == {b"test": b"a value", b"allowed": b""}
        assert list(cert.extensions) == [b"allowed", b"test"]
        signature_key = cert.signature_key()
        assert isinstance(signature_key, ec.EllipticCurvePublicKey)
        assert (
            signature_key.public_numbers()
            == private_key.public_key().public_numbers()
        )

    def test_public_key_errors(self):
        public_key = ec.generate_private_key(ec.SECP256R1()).public_key()
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.public_key(typing.cast(typing.Any, "not a key"))
        builder = builder.public_key(public_key)
        with pytest.raises(ValueError):
            builder.public_key(public_key)

    def test_serial_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.serial(typing.cast(typing.Any, "not a serial"))
        with pytest.raises(ValueError):
            builder.serial(-1)
        with pytest.raises(ValueError):
            builder.serial(2**64)
        builder = builder.serial(1)
        with pytest.raises(ValueError):
            builder.serial(1)

    def test_type_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.type(typing.cast(typing.Any, "not a type"))
        builder = builder.type(SSHCertificateType.USER)
        with pytest.raises(ValueError):
            builder.type(SSHCertificateType.USER)

    def test_key_id_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.key_id(typing.cast(typing.Any, "not bytes"))
        builder = builder.key_id(b"test")
        with pytest.raises(ValueError):
            builder.key_id(b"test")

    def test_valid_principals_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.valid_principals(typing.cast(typing.Any, "not a list"))
        with pytest.raises(TypeError):
            builder.valid_principals(
                [b"test", typing.cast(typing.Any, "not bytes")]
            )
        with pytest.raises(TypeError):
            builder.valid_principals([])
        with pytest.raises(ValueError):
            builder.valid_principals(
                [b"test"] * (ssh._SSHKEY_CERT_MAX_PRINCIPALS + 1)
            )
        builder = builder.valid_principals([b"test"])
        with pytest.raises(ValueError):
            builder.valid_principals([b"test"])
        with pytest.raises(ValueError):
            builder.valid_for_all_principals()

    def test_valid_for_all_principals_errors(self):
        builder = SSHCertificateBuilder()
        builder = builder.valid_for_all_principals()
        with pytest.raises(ValueError):
            builder.valid_for_all_principals()
        with pytest.raises(ValueError):
            builder.valid_principals([b"test"])

    def test_valid_before_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.valid_before(typing.cast(typing.Any, "not an int"))
        with pytest.raises(ValueError):
            builder.valid_before(-1)
        with pytest.raises(ValueError):
            builder.valid_after(2**64)
        builder = builder.valid_before(12345)
        with pytest.raises(ValueError):
            builder.valid_before(123456)

    def test_valid_after_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.valid_after(typing.cast(typing.Any, "not an int"))
        with pytest.raises(ValueError):
            builder.valid_after(-1)
        with pytest.raises(ValueError):
            builder.valid_after(2**64)
        builder = builder.valid_after(1234)
        with pytest.raises(ValueError):
            builder.valid_after(12345)

    def test_add_critical_option_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.add_critical_option(
                typing.cast(typing.Any, "not bytes"),
                b"test",
            )
        with pytest.raises(TypeError):
            builder.add_critical_option(
                b"test",
                typing.cast(typing.Any, object()),
            )
        builder = builder.add_critical_option(b"test", b"test")
        with pytest.raises(ValueError):
            builder.add_critical_option(b"test", b"test")

    def test_add_extension_errors(self):
        builder = SSHCertificateBuilder()
        with pytest.raises(TypeError):
            builder.add_extension(
                typing.cast(typing.Any, "not bytes"),
                b"test",
            )
        with pytest.raises(TypeError):
            builder.add_extension(b"test", typing.cast(typing.Any, object()))
        builder = builder.add_extension(b"test", b"test")
        with pytest.raises(ValueError):
            builder.add_extension(b"test", b"test")

    def test_sign_unsupported_key(self):
        builder = (
            SSHCertificateBuilder()
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(TypeError):
            builder.sign("not a key")

    def test_sign_no_public_key(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_no_type(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_no_valid_principals(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_no_valid_after(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_no_valid_before(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_principals([b"bob"])
            .valid_after(0)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_valid_after_after_valid_before(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_principals([b"eve"])
            .valid_after(20)
            .valid_before(0)
            .type(SSHCertificateType.USER)
        )
        with pytest.raises(ValueError):
            builder.sign(private_key)

    def test_sign_non_zero_serial(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .serial(123456789)
            .valid_principals([b"alice"])
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        cert = builder.sign(private_key)
        assert cert.serial == 123456789

    def test_crit_opts_exts_lexically_sorted(self):
        private_key = ec.generate_private_key(ec.SECP256R1())
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
            .add_critical_option(b"zebra@cryptography.io", b"")
            .add_critical_option(b"apple@cryptography.io", b"")
            .add_critical_option(b"banana@cryptography.io", b"")
            .add_extension(b"zebra@cryptography.io", b"")
            .add_extension(b"apple@cryptography.io", b"")
            .add_extension(b"banana@cryptography.io", b"")
        )
        cert = builder.sign(private_key)
        # This returns a dict, but dicts are order preserving in
        # all our supported versions of Python so we can use
        # items to confirm the order.
        assert list(cert.extensions.items()) == [
            (b"apple@cryptography.io", b""),
            (b"banana@cryptography.io", b""),
            (b"zebra@cryptography.io", b""),
        ]
        assert list(cert.critical_options.items()) == [
            (b"apple@cryptography.io", b""),
            (b"banana@cryptography.io", b""),
            (b"zebra@cryptography.io", b""),
        ]

    def test_sign_ed25519(self):
        private_key = ed25519.Ed25519PrivateKey.generate()
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        cert = builder.sign(private_key)
        assert isinstance(cert.signature_key(), ed25519.Ed25519PublicKey)
        cert.verify_cert_signature()

    @pytest.mark.parametrize(
        "curve", [ec.SECP256R1(), ec.SECP384R1(), ec.SECP521R1()]
    )
    def test_sign_ec(self, curve):
        private_key = ec.generate_private_key(curve)
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        cert = builder.sign(private_key)
        sig_key = cert.signature_key()
        assert isinstance(sig_key, ec.EllipticCurvePublicKey)
        assert isinstance(sig_key.curve, type(curve))
        cert.verify_cert_signature()

    def test_sign_rsa(self):
        private_key = RSA_KEY_2048.private_key()
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(0)
            .valid_before(2**64 - 1)
            .type(SSHCertificateType.USER)
        )
        cert = builder.sign(private_key)
        sig_key = cert.signature_key()
        assert isinstance(sig_key, rsa.RSAPublicKey)
        cert.verify_cert_signature()

    def test_sign_and_byte_compare_rsa(self, monkeypatch):
        # Monkey patch urandom to return a known value so we
        # get a deterministic signature with RSA.
        monkeypatch.setattr(os, "urandom", lambda _: b"\x00" * 32)
        private_key = RSA_KEY_2048.private_key()
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(1672531200)
            .valid_before(1672617600)
            .type(SSHCertificateType.USER)
            .add_extension(b"permit-pty", b"")
            .add_critical_option(
                b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
            )
            .add_critical_option(b"verify-required", b"")
        )
        cert = builder.sign(private_key)
        sig_key = cert.signature_key()
        assert isinstance(sig_key, rsa.RSAPublicKey)
        cert.verify_cert_signature()
        assert cert.public_bytes() == (
            b"ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3Blbn"
            b"NzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA"
            b"QABAAABAQDBevx+d0dMqlqoMDYVij/797UhaFG6IjDl1qv8wcbP71npI+oTMLxZ"
            b"O3OAKrYIpuSjMGUjoxFrpao5ZhRRdOE7bEnpt4Bi5EnXLvsQ/UnpH6CLltBR54L"
            b"p9avFtab3mEgnrbjnPaAPIrLv3Nt26rRu2tmO1lZidD/cbA4zal0M26p9wp5TY1"
            b"4kyHpbLEIVloBjzetoqXK6u8Hjz/APuagONypNDCySDR6M7jM85HDcLoFFrbBb8"
            b"pruHSTxQejMeEmJxYf8b7rNl58/IWPB1ymbNlvHL/4oSOlnrtHkjcxRWzpQ7U3g"
            b"T9BThGyhCiI7EMyEHMgP3r7kTzEUwT6IavWDAAAAAAAAAAAAAAABAAAAAAAAAAA"
            b"AAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5kAAAALAAAAChlY2"
            b"hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhAAAAD3Zlcmlme"
            b"S1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAARcAAAAH"
            b"c3NoLXJzYQAAAAMBAAEAAAEBAMF6/H53R0yqWqgwNhWKP/v3tSFoUboiMOXWq/z"
            b"Bxs/vWekj6hMwvFk7c4Aqtgim5KMwZSOjEWulqjlmFFF04TtsSem3gGLkSdcu+x"
            b"D9SekfoIuW0FHngun1q8W1pveYSCetuOc9oA8isu/c23bqtG7a2Y7WVmJ0P9xsD"
            b"jNqXQzbqn3CnlNjXiTIelssQhWWgGPN62ipcrq7wePP8A+5qA43Kk0MLJINHozu"
            b"MzzkcNwugUWtsFvymu4dJPFB6Mx4SYnFh/xvus2Xnz8hY8HXKZs2W8cv/ihI6We"
            b"u0eSNzFFbOlDtTeBP0FOEbKEKIjsQzIQcyA/evuRPMRTBPohq9YMAAAEUAAAADH"
            b"JzYS1zaGEyLTUxMgAAAQCYbbNzhflDqZAxyBpdLIX0nLAdnTeFNBudMqgo3KGND"
            b"WlU9N17hqBEmcvIOrtNi+JKuKZW89zZrbORHvdjv6NjGSKzJD/XA25YrX1KgMEO"
            b"wt5pzMZX+100drwrjQo+vZqeIN3FJNmT3wssge73v+JsxQrdIAz7YM2OZrFr5HM"
            b"qZEZ5tMvAf/s5YEMDttEU4zMtmjubQyDM5KyYnZdoDT4sKi2rB8gfaigc4IdI/K"
            b"8oXL/3Y7rHuOtejl3lUK4v6DxeRl4aqGYWmhUJc++Rh0cbDgC2S6Cq7gAfG2tND"
            b"zbwL217Q93R08bJn1hDWuiTiaHGauSy2gPUI+cnkvlEocHM"
        )

    def test_sign_and_byte_compare_ed25519(self, monkeypatch):
        # Monkey patch urandom to return a known value so we
        # get a deterministic signature with Ed25519.
        monkeypatch.setattr(os, "urandom", lambda _: b"\x00" * 32)
        private_key = load_vectors_from_file(
            os.path.join("asymmetric", "Ed25519", "ed25519-pkcs8.pem"),
            lambda pemfile: load_pem_private_key(pemfile.read(), None),
            mode="rb",
        )
        assert isinstance(private_key, ed25519.Ed25519PrivateKey)
        builder = (
            SSHCertificateBuilder()
            .public_key(private_key.public_key())
            .valid_for_all_principals()
            .valid_after(1672531200)
            .valid_before(1672617600)
            .type(SSHCertificateType.USER)
            .add_extension(b"permit-pty", b"")
            .add_critical_option(
                b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
            )
            .add_critical_option(b"verify-required", b"")
        )
        cert = builder.sign(private_key)
        sig_key = cert.signature_key()
        assert isinstance(sig_key, ed25519.Ed25519PublicKey)
        cert.verify_cert_signature()
        assert cert.public_bytes() == (
            b"ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdj"
            b"AxQG9wZW5zc2guY29tAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
            b"AAAAAAAINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1EaAAAAAAAAAAAA"
            b"AAABAAAAAAAAAAAAAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5"
            b"kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYW"
            b"FhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAA"
            b"AAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg11qYAYKxCrfVS/7TyWQHOg7hcvPa"
            b"piMlrwIaaPcHURoAAABTAAAAC3NzaC1lZDI1NTE5AAAAQL2aUjeD60C2FrbgHcN"
            b"t8yRa8IRbxvOyA9TZYDGG1dRE3DiR0fuudU20v6vqfTd1gx0S5QyEdECXLl9ZI3"
            b"AwZgc="
        )


class TestSSHSK:
    @staticmethod
    def ssh_str(application):
        data = (
            len(application).to_bytes(length=4, byteorder="big")
            + application.encode()
        )
        return memoryview(data)

    def test_load_application(self):
        ssh.load_application(self.ssh_str("ssh:test"))

    def test_load_application_valueerror(self):
        with pytest.raises(ValueError):
            ssh.load_application(self.ssh_str("hss:test"))


class TestSSHKeyFingerprint:
    @pytest.mark.supported(
        only_if=lambda backend: backend.hash_supported(hashes.MD5()),
        skip_message="Does not support MD5",
    )
    def test_ssh_key_fingerprint_rsa_md5(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.MD5())
        assert fingerprint == b"\x10G\xc2es\xd6QIH\x0b\x81\x1f6\x04{R"

    def test_ssh_key_fingerprint_rsa_sha1(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA1())
        assert fingerprint == b"cQ{L\xf8V\xb5N\x8c\x8e^ni?\xb7(1\xc4\xba\xc5"

    def test_ssh_key_fingerprint_rsa_sha256(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256())
        assert fingerprint == (
            b"\x80\xc0u\xcaV$\xfc\xeb\x04\xb1\x83]\x9a\x1e\xa1\x8d\x17"
            b"\xc4d\xa23\xbek\xa4\xe9 \x92j\x89\xe6\xe8%"
        )

    @pytest.mark.supported(
        only_if=lambda backend: backend.hash_supported(hashes.MD5()),
        skip_message="Does not support MD5 or Ed25519",
    )
    def test_ssh_key_fingerprint_ed25519_md5(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ed25519-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.MD5())
        assert fingerprint == b"\xe5R=\x01\x9e\xa0\xc1\xe9\x8c?L|\xc5\x94W\x85"

    def test_ssh_key_fingerprint_ed25519_sha1(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ed25519-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA1())
        assert fingerprint == b"$\x8a\x8d~p` <\xc4\xdb\x80\xf7*\xaf{)-RX{"

    def test_ssh_key_fingerprint_ed25519_sha256(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ed25519-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256())
        assert fingerprint == (
            b'\x92z-\xb4\xaf\xf4,\x15\xa5\xc6\xf36p83\xcc"]CJi\x16V?\x879'
            b"GZVS8\xb9"
        )

    @pytest.mark.supported(
        only_if=lambda backend: backend.hash_supported(hashes.MD5()),
        skip_message="Does not support MD5",
    )
    def test_ssh_key_fingerprint_ecdsa_md5(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ecdsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.MD5())
        assert fingerprint == b"\re\xf2-\xfaGq\x8c^\x16\xb05+\x06\x1b7"

    def test_ssh_key_fingerprint_ecdsa_sha1(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ecdsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA1())
        assert fingerprint == (
            b"\xdd\xa58\x87cl\x8f\x89\xd9\xd2\x836~\xd1R\xd7\xd7\xf2\x87\xa9"
        )

    def test_ssh_key_fingerprint_ecdsa_sha256(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "ecdsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        fingerprint = ssh_key_fingerprint(public_key, hashes.SHA256())
        assert fingerprint == (
            b"[\xa5\xab\xe9\xdf\r\xe5\x1er\xd6\xbc\xd9\x97\xc2\xf4\xdc"
            b"\xd4\xe0\xaf\x17<i\xdc^\x11>\xc6\x91wIJ\xf5"
        )

    def test_ssh_key_fingerprint_unsupported_hash(self):
        ssh_key = load_vectors_from_file(
            os.path.join("asymmetric", "OpenSSH", "rsa-nopsw.key.pub"),
            lambda f: f.read(),
            mode="rb",
        )
        public_key = load_ssh_public_key(ssh_key)
        with pytest.raises(TypeError):
            ssh_key_fingerprint(
                public_key, typing.cast(typing.Any, hashes.SM3())
            )

    def test_ssh_key_fingerprint_unsupported_key(self):
        with pytest.raises(ValueError):
            ssh_key_fingerprint(
                typing.cast(typing.Any, object()), hashes.SHA256()
            )
