# Copyright 2005 Divmod, Inc.  See LICENSE file for details
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

"""
Tests for L{twisted.internet._sslverify}.
"""

from __future__ import annotations

import datetime
import gc
import itertools
import textwrap
from collections.abc import Iterable
from dataclasses import dataclass
from weakref import ref

from zope.interface import implementer

from incremental import Version

from twisted.internet import defer, interfaces, protocol, reactor
from twisted.internet._idna import _idnaText
from twisted.internet.address import IPv4Address
from twisted.internet.error import CertificateError, ConnectionClosed, ConnectionLost
from twisted.internet.interfaces import (
    IOpenSSLClientConnectionCreator,
    IOpenSSLContextFactory,
    IProtocol,
    IProtocolNegotiationFactory,
)
from twisted.internet.task import Clock
from twisted.python.compat import nativeString
from twisted.python.failure import Failure
from twisted.python.filepath import FilePath
from twisted.python.modules import getModule
from twisted.python.reflect import requireModule
from twisted.test.iosim import IOPump, connectedServerAndClient
from twisted.trial import util
from twisted.trial.unittest import SkipTest, SynchronousTestCase, TestCase

skipSSL = ""

if requireModule("OpenSSL"):
    import ipaddress

    from OpenSSL import SSL
    from OpenSSL.crypto import FILETYPE_PEM, TYPE_RSA, X509, PKey, get_elliptic_curves

    from cryptography import x509
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric.rsa import (
        RSAPrivateKey,
        generate_private_key,
    )
    from cryptography.hazmat.primitives.serialization import (
        Encoding,
        NoEncryption,
        PrivateFormat,
    )
    from cryptography.x509.oid import NameOID

    from twisted.internet import ssl
else:
    skipSSL = "OpenSSL is required for SSL tests."

if not skipSSL:
    from twisted.internet import _sslverify as sslverify
    from twisted.internet.ssl import (
        VerificationError,
        optionsForClientTLS,
        platformTrust,
    )
    from twisted.protocols.tls import (
        SomeConnectionCreator,
        TLSMemoryBIOFactory,
        TLSMemoryBIOProtocol,
    )

# A couple of static PEM-format certificates to be used by various tests.
A_HOST_CERTIFICATE_PEM = """
-----BEGIN CERTIFICATE-----
        MIIC2jCCAkMCAjA5MA0GCSqGSIb3DQEBBAUAMIG0MQswCQYDVQQGEwJVUzEiMCAG
        A1UEAxMZZXhhbXBsZS50d2lzdGVkbWF0cml4LmNvbTEPMA0GA1UEBxMGQm9zdG9u
        MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMRYwFAYDVQQIEw1NYXNzYWNo
        dXNldHRzMScwJQYJKoZIhvcNAQkBFhhub2JvZHlAdHdpc3RlZG1hdHJpeC5jb20x
        ETAPBgNVBAsTCFNlY3VyaXR5MB4XDTA2MDgxNjAxMDEwOFoXDTA3MDgxNjAxMDEw
        OFowgbQxCzAJBgNVBAYTAlVTMSIwIAYDVQQDExlleGFtcGxlLnR3aXN0ZWRtYXRy
        aXguY29tMQ8wDQYDVQQHEwZCb3N0b24xHDAaBgNVBAoTE1R3aXN0ZWQgTWF0cml4
        IExhYnMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxJzAlBgkqhkiG9w0BCQEWGG5v
        Ym9keUB0d2lzdGVkbWF0cml4LmNvbTERMA8GA1UECxMIU2VjdXJpdHkwgZ8wDQYJ
        KoZIhvcNAQEBBQADgY0AMIGJAoGBAMzH8CDF/U91y/bdbdbJKnLgnyvQ9Ig9ZNZp
        8hpsu4huil60zF03+Lexg2l1FIfURScjBuaJMR6HiMYTMjhzLuByRZ17KW4wYkGi
        KXstz03VIKy4Tjc+v4aXFI4XdRw10gGMGQlGGscXF/RSoN84VoDKBfOMWdXeConJ
        VyC4w3iJAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAviMT4lBoxOgQy32LIgZ4lVCj
        JNOiZYg8GMQ6y0ugp86X80UjOvkGtNf/R7YgED/giKRN/q/XJiLJDEhzknkocwmO
        S+4b2XpiaZYxRyKWwL221O7CGmtWYyZl2+92YYmmCiNzWQPfP6BOMlfax0AGLHls
        fXzCWdG0O/3Lk2SRM0I=
-----END CERTIFICATE-----
"""

A_PEER_CERTIFICATE_PEM = """
-----BEGIN CERTIFICATE-----
        MIIC3jCCAkcCAjA6MA0GCSqGSIb3DQEBBAUAMIG2MQswCQYDVQQGEwJVUzEiMCAG
        A1UEAxMZZXhhbXBsZS50d2lzdGVkbWF0cml4LmNvbTEPMA0GA1UEBxMGQm9zdG9u
        MRwwGgYDVQQKExNUd2lzdGVkIE1hdHJpeCBMYWJzMRYwFAYDVQQIEw1NYXNzYWNo
        dXNldHRzMSkwJwYJKoZIhvcNAQkBFhpzb21lYm9keUB0d2lzdGVkbWF0cml4LmNv
        bTERMA8GA1UECxMIU2VjdXJpdHkwHhcNMDYwODE2MDEwMTU2WhcNMDcwODE2MDEw
        MTU2WjCBtjELMAkGA1UEBhMCVVMxIjAgBgNVBAMTGWV4YW1wbGUudHdpc3RlZG1h
        dHJpeC5jb20xDzANBgNVBAcTBkJvc3RvbjEcMBoGA1UEChMTVHdpc3RlZCBNYXRy
        aXggTGFiczEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czEpMCcGCSqGSIb3DQEJARYa
        c29tZWJvZHlAdHdpc3RlZG1hdHJpeC5jb20xETAPBgNVBAsTCFNlY3VyaXR5MIGf
        MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnm+WBlgFNbMlHehib9ePGGDXF+Nz4
        CjGuUmVBaXCRCiVjg3kSDecwqfb0fqTksBZ+oQ1UBjMcSh7OcvFXJZnUesBikGWE
        JE4V8Bjh+RmbJ1ZAlUPZ40bAkww0OpyIRAGMvKG+4yLFTO4WDxKmfDcrOb6ID8WJ
        e1u+i3XGkIf/5QIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAD4Oukm3YYkhedUepBEA
        vvXIQhVDqL7mk6OqYdXmNj6R7ZMC8WWvGZxrzDI1bZuB+4aIxxd1FXC3UOHiR/xg
        i9cDl1y8P/qRp4aEBNF6rI0D4AxTbfnHQx4ERDAOShJdYZs/2zifPJ6va6YvrEyr
        yqDtGhklsWW3ZwBzEh5VEOUp
-----END CERTIFICATE-----
"""

A_KEYPAIR = getModule(__name__).filePath.sibling("server.pem").getContent()


def counter(counter=itertools.count()):
    """
    Each time we're called, return the next integer in the natural numbers.
    """
    return next(counter)


def makeCertificate(**kw):
    keypair = PKey()
    keypair.generate_key(TYPE_RSA, 2048)

    certificate = X509()
    certificate.gmtime_adj_notBefore(0)
    certificate.gmtime_adj_notAfter(60 * 60 * 24 * 365)  # One year
    for xname in certificate.get_issuer(), certificate.get_subject():
        for k, v in kw.items():
            setattr(xname, k, nativeString(v))

    certificate.set_serial_number(counter())
    certificate.set_pubkey(keypair)
    certificate.sign(keypair, "md5")

    return keypair, certificate


oneDay = datetime.timedelta(1, 0, 0)


@dataclass
class TestingAuthority:
    name: x509.Name
    cert: x509.Certificate
    key: RSAPrivateKey

    @classmethod
    def create(
        cls, aroundTimestamp: datetime.datetime = datetime.datetime.today()
    ) -> TestingAuthority:
        commonNameForCA = x509.Name(
            [x509.NameAttribute(NameOID.COMMON_NAME, "Testing Example CA")]
        )
        privateKeyForCA = generate_private_key(
            public_exponent=65537, key_size=4096, backend=default_backend()
        )
        publicKeyForCA = privateKeyForCA.public_key()
        caCertificate = (
            x509.CertificateBuilder()
            .subject_name(commonNameForCA)
            .issuer_name(commonNameForCA)
            .not_valid_before(aroundTimestamp - oneDay)
            .not_valid_after(aroundTimestamp + oneDay)
            .serial_number(x509.random_serial_number())
            .public_key(publicKeyForCA)
            .add_extension(
                x509.BasicConstraints(ca=True, path_length=9),
                critical=True,
            )
            .sign(
                private_key=privateKeyForCA,
                algorithm=hashes.SHA256(),
                backend=default_backend(),
            )
        )

        return TestingAuthority(commonNameForCA, caCertificate, privateKeyForCA)

    def serverCertificate(
        self, commonName: str, subjects: list[str]
    ) -> sslverify.PrivateCertificate:
        privateKeyForServer = generate_private_key(
            public_exponent=65537, key_size=4096, backend=default_backend()
        )
        publicKeyForServer = privateKeyForServer.public_key()
        commonNameForServer = x509.Name(
            [x509.NameAttribute(NameOID.COMMON_NAME, commonName)]
        )

        subjectAlternativeNames: list[x509.IPAddress | x509.DNSName] = []
        for subject in subjects:
            try:
                ipAddress = ipaddress.ip_address(subject)
            except ValueError:
                subjectAlternativeNames.append(
                    x509.DNSName(subject.encode("idna").decode("ascii"))
                )
            else:
                subjectAlternativeNames.append(x509.IPAddress(ipAddress))

        serverBuilder = (
            x509.CertificateBuilder()
            .subject_name(commonNameForServer)
            .not_valid_before(datetime.datetime.today() - oneDay)
            .not_valid_after(datetime.datetime.today() + oneDay)
            .serial_number(x509.random_serial_number())
            .public_key(publicKeyForServer)
            .add_extension(
                x509.BasicConstraints(ca=False, path_length=None),
                critical=True,
            )
            .add_extension(
                x509.SubjectAlternativeName(subjectAlternativeNames),
                critical=True,
            )
        )
        signedX509 = self.authoritize(serverBuilder)
        serverCert: sslverify.PrivateCertificate = sslverify.PrivateCertificate.loadPEM(
            b"\n".join(
                [
                    privateKeyForServer.private_bytes(
                        Encoding.PEM,
                        PrivateFormat.TraditionalOpenSSL,
                        NoEncryption(),
                    ),
                    signedX509.public_bytes(Encoding.PEM),
                ]
            )
        )

        return serverCert

    def authorityCertificate(self) -> sslverify.Certificate:
        return sslverify.Certificate.loadPEM(self.cert.public_bytes(Encoding.PEM))

    def authoritize(self, builder: x509.CertificateBuilder) -> x509.Certificate:
        return builder.issuer_name(self.name).sign(
            private_key=self.key,
            algorithm=hashes.SHA256(),
            backend=default_backend(),
        )


def certificatesForAuthorityAndServer(
    serviceIdentity: str = "example.com",
) -> tuple[sslverify.Certificate, sslverify.PrivateCertificate]:
    """
    Create a self-signed CA certificate and server certificate signed by the
    CA.

    @param serviceIdentity: The identity (hostname) of the server.
    @type serviceIdentity: L{unicode}

    @return: a 2-tuple of C{(certificate_authority_certificate,
        server_certificate)}
    @rtype: L{tuple} of (L{sslverify.Certificate},
        L{sslverify.PrivateCertificate})
    """
    authority = TestingAuthority.create()
    return (
        authority.authorityCertificate(),
        authority.serverCertificate("Testing Example Server", [serviceIdentity]),
    )


class GreetingServer(protocol.Protocol):
    greeting = b"greetings!"

    def connectionMade(self):
        self.transport.write(self.greeting)


class ListeningClient(protocol.Protocol):
    data = b""
    lostReason = None

    def dataReceived(self, data):
        self.data += data

    def connectionLost(self, reason):
        self.lostReason = reason


@implementer(IProtocolNegotiationFactory)
class NegotiatingFactory(protocol.Factory):
    """
    A L{ClientFactory} that has a set of acceptable protocols for ALPN
    negotiation.
    """

    def __init__(self, acceptableProtocols: list[bytes]) -> None:
        """
        Initialize this mixin.

        @param acceptableProtocols: The protocols the client will accept
            speaking after the TLS handshake is complete.
        @type acceptableProtocols: L{list} of L{bytes}
        """
        self._acceptableProtocols = acceptableProtocols

    def acceptableProtocols(self) -> list[bytes]:
        """
        Returns a list of protocols that can be spoken by the connection
        factory in the form of ALPN tokens, as laid out in the IANA registry
        for ALPN tokens.

        @return: a list of ALPN tokens in order of preference.
        @rtype: L{list} of L{bytes}
        """
        return self._acceptableProtocols


def _loopbackTLSConnection(
    serverOpts: SomeConnectionCreator,
    clientOpts: SomeConnectionCreator,
    serverNegotiationProtocols: list[bytes] | None = None,
    clientNegotiationProtocols: list[bytes] | None = None,
) -> tuple[
    TLSMemoryBIOProtocol, TLSMemoryBIOProtocol, GreetingServer, ListeningClient, IOPump
]:
    """
    Common implementation code for both L{loopbackTLSConnection} and
    L{loopbackTLSConnectionInMemory}.  Creates a loopback TLS connection using
    the provided server and client context factories.

    @param serverOpts: An OpenSSL connection creator for the server.

    @param clientOpts: An OpenSSL connection creator for the client.

    @param serverNegotiationProtocols: The ALPN protocols to specify via the
        protocol factory L{IProtocolNegotiationFactory} hook on the server.

    @param clientNegotiationProtocols: The ALPN protocols to specify via the
        protocol factory L{IProtocolNegotiationFactory} hook on the client.

    @return: 5-tuple of server-tls-protocol, server-inner-protocol,
        client-tls-protocol, client-inner-protocol and L{IOPump}
    @rtype: L{tuple}
    """

    clientWrappedProto = ListeningClient()
    serverWrappedProto = GreetingServer()

    plainClientFactory = (
        protocol.Factory()
        if clientNegotiationProtocols is None
        else NegotiatingFactory(clientNegotiationProtocols)
    )
    plainClientFactory.protocol = lambda: clientWrappedProto
    plainServerFactory = (
        protocol.Factory()
        if serverNegotiationProtocols is None
        else NegotiatingFactory(serverNegotiationProtocols)
    )
    plainServerFactory.protocol = lambda: serverWrappedProto

    clock = Clock()
    clientFactory = TLSMemoryBIOFactory(
        clientOpts, isClient=True, wrappedFactory=plainServerFactory, clock=clock
    )
    serverFactory = TLSMemoryBIOFactory(
        serverOpts, isClient=False, wrappedFactory=plainClientFactory, clock=clock
    )

    sProto, cProto, pump = connectedServerAndClient(
        lambda: serverFactory.buildProtocol(None),
        lambda: clientFactory.buildProtocol(None),
        clock=clock,
    )
    pump.flush()

    return sProto, cProto, serverWrappedProto, clientWrappedProto, pump


def loopbackTLSConnection(
    trustRoot: sslverify.IOpenSSLTrustRoot,
    privateKeyFile: str,
    chainedCertFile: str | None = None,
) -> tuple[
    TLSMemoryBIOProtocol, TLSMemoryBIOProtocol, GreetingServer, ListeningClient, IOPump
]:
    """
    Create a loopback TLS connection with the given trust and keys.

    @param trustRoot: the C{trustRoot} argument for the client connection's
        context.
    @type trustRoot: L{sslverify.IOpenSSLTrustRoot}

    @param privateKeyFile: The name of the file containing the private key.
    @type privateKeyFile: L{str} (native string; file name)

    @param chainedCertFile: The name of the chained certificate file.
    @type chainedCertFile: L{str} (native string; file name)

    @return: 3-tuple of server-protocol, client-protocol, and L{IOPump}
    @rtype: L{tuple}
    """

    @implementer(IOpenSSLContextFactory)
    class ContextFactory:
        def getContext(self):
            """
            Create a context for the server side of the connection.

            @return: an SSL context using a certificate and key.
            @rtype: C{OpenSSL.SSL.Context}
            """
            ctx = SSL.Context(SSL.SSLv23_METHOD)
            if chainedCertFile is not None:
                ctx.use_certificate_chain_file(chainedCertFile)
            ctx.use_privatekey_file(privateKeyFile)
            # Let the test author know if they screwed something up.
            ctx.check_privatekey()
            return ctx

    serverOpts = ContextFactory()
    clientOpts = sslverify.OpenSSLCertificateOptions(trustRoot=trustRoot)

    return _loopbackTLSConnection(serverOpts, clientOpts)


def loopbackTLSConnectionInMemory(
    trustRoot: sslverify.IOpenSSLTrustRoot,
    privateKey: PKey,
    serverCertificate: X509,
    serverProtocols: list[bytes] | None = None,
    clientProtocols: list[bytes] | None = None,
    viaFactory: bool = False,
    clientOptionsHost: str | None = None,
) -> tuple[
    TLSMemoryBIOProtocol, TLSMemoryBIOProtocol, GreetingServer, ListeningClient, IOPump
]:
    """
    Create a loopback TLS connection with the given trust and keys.  Like
    L{loopbackTLSConnection}, but using in-memory certificates and keys rather
    than writing them to disk.

    @param trustRoot: the C{trustRoot} argument for the client connection's
        context.

    @param privateKey: The private key.

    @param serverCertificate: The certificate used by the server.

    @type chainedCertFile: L{str} (native string)

    @param clientProtocols: The protocols the client is willing to negotiate
        using ALPN.

    @param serverProtocols: The protocols the server is willing to negotiate
        using ALPN.

    @param viaFactory: If True, pass the protocols along via the
        L{IProtocolNegotiationFactory} hook rather than the
        C{acceptableProtocols} arguemnt to
        L{sslverify.OpenSSLCertificateOptions}.

    @return: 5-tuple of server-tls-protocol, client-tls-protocol,
        server-app-protocol, client-app-protocol, L{IOPump}
    """
    clientCertOpts: IOpenSSLClientConnectionCreator
    if clientOptionsHost is not None:
        clientCertOpts = sslverify.optionsForClientTLS(
            clientOptionsHost,
            trustRoot=trustRoot,
            acceptableProtocols=clientProtocols if not viaFactory else None,
        )
    else:
        clientCertOpts = sslverify.OpenSSLCertificateOptions(
            trustRoot=trustRoot,
            acceptableProtocols=clientProtocols if not viaFactory else None,
        )
    serverCertOpts = sslverify.OpenSSLCertificateOptions(
        privateKey=privateKey,
        certificate=serverCertificate,
        acceptableProtocols=serverProtocols if not viaFactory else None,
    )
    return _loopbackTLSConnection(
        serverCertOpts,
        clientCertOpts,
        serverProtocols if viaFactory else None,
        clientProtocols if viaFactory else None,
    )


def pathContainingDumpOf(testCase, *dumpables):
    """
    Create a temporary file to store some serializable-as-PEM objects in, and
    return its name.

    @param testCase: a test case to use for generating a temporary directory.
    @type testCase: L{twisted.trial.unittest.TestCase}

    @param dumpables: arguments are objects from pyOpenSSL with a C{dump}
        method, taking a pyOpenSSL file-type constant, such as
        L{OpenSSL.crypto.FILETYPE_PEM} or L{OpenSSL.crypto.FILETYPE_ASN1}.
    @type dumpables: L{tuple} of L{object} with C{dump} method taking L{int}
        returning L{bytes}

    @return: the path to a file where all of the dumpables were dumped in PEM
        format.
    @rtype: L{str}
    """
    fname = testCase.mktemp()
    with open(fname, "wb") as f:
        for dumpable in dumpables:
            f.write(dumpable.dump(FILETYPE_PEM))
    return fname


class DataCallbackProtocol(protocol.Protocol):
    def dataReceived(self, data):
        d, self.factory.onData = self.factory.onData, None
        if d is not None:
            d.callback(data)

    def connectionLost(self, reason):
        d, self.factory.onLost = self.factory.onLost, None
        if d is not None:
            d.errback(reason)


class WritingProtocol(protocol.Protocol):
    byte = b"x"

    def connectionMade(self):
        self.transport.write(self.byte)

    def connectionLost(self, reason):
        self.factory.onLost.errback(reason)


class FakeContext:
    """
    Introspectable fake of an C{OpenSSL.SSL.Context}.

    Saves call arguments for later introspection.

    Necessary because C{Context} offers poor introspection.  cf. this
    U{pyOpenSSL bug<https://bugs.launchpad.net/pyopenssl/+bug/1173899>}.

    @ivar _method: See C{method} parameter of L{__init__}.

    @ivar _options: L{int} of C{OR}ed values from calls of L{set_options}.

    @ivar _certificate: Set by L{use_certificate}.

    @ivar _privateKey: Set by L{use_privatekey}.

    @ivar _verify: Set by L{set_verify}.

    @ivar _verifyDepth: Set by L{set_verify_depth}.

    @ivar _mode: Set by L{set_mode}.

    @ivar _sessionID: Set by L{set_session_id}.

    @ivar _extraCertChain: Accumulated L{list} of all extra certificates added
        by L{add_extra_chain_cert}.

    @ivar _cipherList: Set by L{set_cipher_list}.

    @ivar _dhFilename: Set by L{load_tmp_dh}.

    @ivar _defaultVerifyPathsSet: Set by L{set_default_verify_paths}

    @ivar _ecCurve: Set by L{set_tmp_ecdh}
    """

    _options = 0

    def __init__(self, method):
        self._method = method
        self._extraCertChain = []
        self._defaultVerifyPathsSet = False
        self._ecCurve = None

        # Note that this value is explicitly documented as the default by
        # https://www.openssl.org/docs/man1.1.1/man3/
        # SSL_CTX_set_session_cache_mode.html
        self._sessionCacheMode = SSL.SESS_CACHE_SERVER

    def set_options(self, options):
        self._options |= options

    def set_alpn_select_callback(self, callback):
        self._alpn_select_callback = callback

    def use_certificate(self, certificate):
        self._certificate = certificate

    def use_privatekey(self, privateKey):
        self._privateKey = privateKey

    def check_privatekey(self):
        return None

    def set_mode(self, mode):
        """
        Set the mode. See L{SSL.Context.set_mode}.

        @param mode: See L{SSL.Context.set_mode}.
        """
        self._mode = mode

    def set_verify(self, flags, callback=None):
        self._verify = flags, callback

    def set_verify_depth(self, depth):
        self._verifyDepth = depth

    def set_session_id(self, sessionIDContext):
        # This fake should change when the upstream changes:
        # https://github.com/pyca/pyopenssl/issues/845
        self._sessionIDContext = sessionIDContext

    def set_session_cache_mode(self, cacheMode):
        """
        Set the session cache mode on the context, as per
        L{SSL.Context.set_session_cache_mode}.
        """
        self._sessionCacheMode = cacheMode

    def get_session_cache_mode(self):
        """
        Retrieve the session cache mode from the context, as per
        L{SSL.Context.get_session_cache_mode}.
        """
        return self._sessionCacheMode

    def add_extra_chain_cert(self, cert):
        self._extraCertChain.append(cert)

    def set_cipher_list(self, cipherList):
        self._cipherList = cipherList

    def load_tmp_dh(self, dhfilename):
        self._dhFilename = dhfilename

    def set_default_verify_paths(self):
        """
        Set the default paths for the platform.
        """
        self._defaultVerifyPathsSet = True

    def set_tmp_ecdh(self, curve):
        """
        Set an ECDH curve.  Should only be called by OpenSSL 1.0.1
        code.

        @param curve: See L{OpenSSL.SSL.Context.set_tmp_ecdh}
        """
        self._ecCurve = curve


class ClientOptionsTests(SynchronousTestCase):
    """
    Tests for L{sslverify.optionsForClientTLS}.
    """

    if skipSSL:
        skip = skipSSL

    def test_extraKeywords(self):
        """
        When passed a keyword parameter other than C{extraCertificateOptions},
        L{sslverify.optionsForClientTLS} raises an exception just like a
        normal Python function would.
        """
        error = self.assertRaises(
            TypeError,
            sslverify.optionsForClientTLS,
            hostname="alpha",
            someRandomThing="beta",
        )
        self.assertEqual(
            str(error),
            "optionsForClientTLS() got an unexpected keyword argument "
            "'someRandomThing'",
        )

    def test_bytesFailFast(self):
        """
        If you pass L{bytes} as the hostname to
        L{sslverify.optionsForClientTLS} it immediately raises a L{TypeError}.
        """
        error = self.assertRaises(
            TypeError, sslverify.optionsForClientTLS, b"not-actually-a-hostname.com"
        )
        expectedText = (
            "optionsForClientTLS requires text for host names, not " + bytes.__name__
        )
        self.assertEqual(str(error), expectedText)

    def test_dNSNameHostname(self):
        """
        If you pass a dNSName to L{sslverify.optionsForClientTLS}
        L{_hostnameIsDnsName} will be True
        """
        options = sslverify.optionsForClientTLS("example.com")
        self.assertTrue(options._hostnameIsDnsName)

    def test_IPv4AddressHostname(self):
        """
        If you pass an IPv4 address to L{sslverify.optionsForClientTLS}
        L{_hostnameIsDnsName} will be False
        """
        options = sslverify.optionsForClientTLS("127.0.0.1")
        self.assertFalse(options._hostnameIsDnsName)

    def test_IPv6AddressHostname(self):
        """
        If you pass an IPv6 address to L{sslverify.optionsForClientTLS}
        L{_hostnameIsDnsName} will be False
        """
        options = sslverify.optionsForClientTLS("::1")
        self.assertFalse(options._hostnameIsDnsName)


class FakeChooseDiffieHellmanEllipticCurve:
    """
    A fake implementation of L{_ChooseDiffieHellmanEllipticCurve}
    """

    def __init__(self, versionNumber, openSSLlib, openSSLcrypto):
        """
        A no-op constructor.
        """

    def configureECDHCurve(self, ctx):
        """
        A null configuration.

        @param ctx: An L{OpenSSL.SSL.Context} that would be
            configured.
        """


class OpenSSLOptionsTestsMixin:
    """
    A mixin for L{OpenSSLOptions} test cases creates client and server
    certificates, signs them with a CA, and provides a L{loopback}
    that creates TLS a connections with them.
    """

    if skipSSL:
        skip = skipSSL

    serverPort = clientConn = None
    onServerLost = onClientLost = None

    def setUp(self):
        """
        Create class variables of client and server certificates.
        """
        self.sKey, self.sCert = makeCertificate(
            O=b"Server Test Certificate", CN=b"server"
        )
        self.cKey, self.cCert = makeCertificate(
            O=b"Client Test Certificate", CN=b"client"
        )
        self.caCert1 = makeCertificate(O=b"CA Test Certificate 1", CN=b"ca1")[1]
        self.caCert2 = makeCertificate(O=b"CA Test Certificate", CN=b"ca2")[1]
        self.caCerts = [self.caCert1, self.caCert2]
        self.extraCertChain = self.caCerts

    def tearDown(self):
        if self.serverPort is not None:
            self.serverPort.stopListening()
        if self.clientConn is not None:
            self.clientConn.disconnect()

        L = []
        if self.onServerLost is not None:
            L.append(self.onServerLost)
        if self.onClientLost is not None:
            L.append(self.onClientLost)

        return defer.DeferredList(L, consumeErrors=True)

    def loopback(
        self,
        serverCertOpts,
        clientCertOpts,
        onServerLost=None,
        onClientLost=None,
        onData=None,
    ):
        if onServerLost is None:
            self.onServerLost = onServerLost = defer.Deferred()
        if onClientLost is None:
            self.onClientLost = onClientLost = defer.Deferred()
        if onData is None:
            onData = defer.Deferred()

        serverFactory = protocol.ServerFactory()
        serverFactory.protocol = DataCallbackProtocol
        serverFactory.onLost = onServerLost
        serverFactory.onData = onData

        clientFactory = protocol.ClientFactory()
        clientFactory.protocol = WritingProtocol
        clientFactory.onLost = onClientLost

        self.serverPort = reactor.listenSSL(0, serverFactory, serverCertOpts)
        self.clientConn = reactor.connectSSL(
            "127.0.0.1", self.serverPort.getHost().port, clientFactory, clientCertOpts
        )


class OpenSSLOptionsTests(OpenSSLOptionsTestsMixin, TestCase):
    """
    Tests for L{sslverify.OpenSSLOptions}.
    """

    def setUp(self):
        """
        Same as L{OpenSSLOptionsTestsMixin.setUp}, but it also patches
        L{sslverify._ChooseDiffieHellmanEllipticCurve}.
        """
        super().setUp()
        self.patch(
            sslverify,
            "_ChooseDiffieHellmanEllipticCurve",
            FakeChooseDiffieHellmanEllipticCurve,
        )

    def test_constructorWithOnlyPrivateKey(self):
        """
        C{privateKey} and C{certificate} make only sense if both are set.
        """
        self.assertRaises(
            ValueError, sslverify.OpenSSLCertificateOptions, privateKey=self.sKey
        )

    def test_constructorWithOnlyCertificate(self):
        """
        C{privateKey} and C{certificate} make only sense if both are set.
        """
        self.assertRaises(
            ValueError, sslverify.OpenSSLCertificateOptions, certificate=self.sCert
        )

    def test_constructorWithCertificateAndPrivateKey(self):
        """
        Specifying C{privateKey} and C{certificate} initializes correctly.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey, certificate=self.sCert
        )
        self.assertEqual(opts.privateKey, self.sKey)
        self.assertEqual(opts.certificate, self.sCert)
        self.assertEqual(opts.extraCertChain, [])

    def test_constructorDoesNotAllowVerifyWithoutCACerts(self):
        """
        C{verify} must not be C{True} without specifying C{caCerts}.
        """
        self.assertRaises(
            ValueError,
            sslverify.OpenSSLCertificateOptions,
            privateKey=self.sKey,
            certificate=self.sCert,
            verify=True,
        )

    def test_constructorDoesNotAllowLegacyWithTrustRoot(self):
        """
        C{verify}, C{requireCertificate}, and C{caCerts} must not be specified
        by the caller (to be I{any} value, even the default!) when specifying
        C{trustRoot}.
        """
        self.assertRaises(
            TypeError,
            sslverify.OpenSSLCertificateOptions,
            privateKey=self.sKey,
            certificate=self.sCert,
            verify=True,
            trustRoot=None,
            caCerts=self.caCerts,
        )
        self.assertRaises(
            TypeError,
            sslverify.OpenSSLCertificateOptions,
            privateKey=self.sKey,
            certificate=self.sCert,
            trustRoot=None,
            requireCertificate=True,
        )

    def test_constructorAllowsCACertsWithoutVerify(self):
        """
        It's currently a NOP, but valid.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey, certificate=self.sCert, caCerts=self.caCerts
        )
        self.assertFalse(opts.verify)
        self.assertEqual(self.caCerts, opts.caCerts)

    def test_constructorWithVerifyAndCACerts(self):
        """
        Specifying C{verify} and C{caCerts} initializes correctly.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            verify=True,
            caCerts=self.caCerts,
        )
        self.assertTrue(opts.verify)
        self.assertEqual(self.caCerts, opts.caCerts)

    def test_constructorSetsExtraChain(self):
        """
        Setting C{extraCertChain} works if C{certificate} and C{privateKey} are
        set along with it.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            extraCertChain=self.extraCertChain,
        )
        self.assertEqual(self.extraCertChain, opts.extraCertChain)

    def test_constructorDoesNotAllowExtraChainWithoutPrivateKey(self):
        """
        A C{extraCertChain} without C{privateKey} doesn't make sense and is
        thus rejected.
        """
        self.assertRaises(
            ValueError,
            sslverify.OpenSSLCertificateOptions,
            certificate=self.sCert,
            extraCertChain=self.extraCertChain,
        )

    def test_constructorDoesNotAllowExtraChainWithOutPrivateKey(self):
        """
        A C{extraCertChain} without C{certificate} doesn't make sense and is
        thus rejected.
        """
        self.assertRaises(
            ValueError,
            sslverify.OpenSSLCertificateOptions,
            privateKey=self.sKey,
            extraCertChain=self.extraCertChain,
        )

    def test_extraChainFilesAreAddedIfSupplied(self):
        """
        If C{extraCertChain} is set and all prerequisites are met, the
        specified chain certificates are added to C{Context}s that get
        created.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            extraCertChain=self.extraCertChain,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        self.assertEqual(self.sKey, ctx._privateKey)
        self.assertEqual(self.sCert, ctx._certificate)
        self.assertEqual(self.extraCertChain, ctx._extraCertChain)

    def test_extraChainDoesNotBreakPyOpenSSL(self):
        """
        C{extraCertChain} doesn't break C{OpenSSL.SSL.Context} creation.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            extraCertChain=self.extraCertChain,
        )
        ctx = opts.getContext()
        self.assertIsInstance(ctx, SSL.Context)

    def test_acceptableCiphersAreAlwaysSet(self):
        """
        If the user doesn't supply custom acceptable ciphers, a shipped secure
        default is used.  We can't check directly for it because the effective
        cipher string we set varies with platforms.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        self.assertEqual(opts._cipherString.encode("ascii"), ctx._cipherList)

    def test_givesMeaningfulErrorMessageIfNoCipherMatches(self):
        """
        If there is no valid cipher that matches the user's wishes,
        a L{ValueError} is raised.
        """
        self.assertRaises(
            ValueError,
            sslverify.OpenSSLCertificateOptions,
            privateKey=self.sKey,
            certificate=self.sCert,
            acceptableCiphers=sslverify.OpenSSLAcceptableCiphers.fromOpenSSLCipherString(
                ""
            ),
        )

    def test_honorsAcceptableCiphersArgument(self):
        """
        If acceptable ciphers are passed, they are used.
        """

        @implementer(interfaces.IAcceptableCiphers)
        class FakeAcceptableCiphers:
            def selectCiphers(self, _):
                return [sslverify.OpenSSLCipher("sentinel")]

        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            acceptableCiphers=FakeAcceptableCiphers(),
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        self.assertEqual(b"sentinel", ctx._cipherList)

    def test_basicSecurityOptionsAreSet(self):
        """
        Every context must have C{OP_NO_SSLv2}, C{OP_NO_COMPRESSION}, and
        C{OP_CIPHER_SERVER_PREFERENCE} set.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2 | SSL.OP_NO_COMPRESSION | SSL.OP_CIPHER_SERVER_PREFERENCE
        )
        self.assertEqual(options, ctx._options & options)

    def test_modeIsSet(self):
        """
        Every context must be in C{MODE_RELEASE_BUFFERS} mode.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        self.assertEqual(SSL.MODE_RELEASE_BUFFERS, ctx._mode)

    def test_singleUseKeys(self):
        """
        If C{singleUseKeys} is set, every context must have
        C{OP_SINGLE_DH_USE} and C{OP_SINGLE_ECDH_USE} set.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            enableSingleUseKeys=True,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = SSL.OP_SINGLE_DH_USE | SSL.OP_SINGLE_ECDH_USE
        self.assertEqual(options, ctx._options & options)

    def test_methodIsDeprecated(self):
        """
        Passing C{method} to L{sslverify.OpenSSLCertificateOptions} is
        deprecated.
        """
        sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            method=SSL.SSLv23_METHOD,
        )

        message = (
            "Passing method to twisted.internet.ssl.CertificateOptions "
            "was deprecated in Twisted 17.1.0. Please use a "
            "combination of insecurelyLowerMinimumTo, raiseMinimumTo, "
            "and lowerMaximumSecurityTo instead, as Twisted will "
            "correctly configure the method."
        )

        warnings = self.flushWarnings([self.test_methodIsDeprecated])
        self.assertEqual(1, len(warnings))
        self.assertEqual(DeprecationWarning, warnings[0]["category"])
        self.assertEqual(message, warnings[0]["message"])

    def test_tlsv12ByDefault(self):
        """
        L{sslverify.OpenSSLCertificateOptions} will make the default minimum
        TLS version v1.2, if no C{method}, or C{insecurelyLowerMinimumTo} is
        given.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey, certificate=self.sCert
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
        )
        self.assertEqual(options, ctx._options & options)

    def test_tlsProtocolsAtLeastWithMinimum(self):
        """
        Passing C{insecurelyLowerMinimumTo} along with C{raiseMinimumTo} to
        L{sslverify.OpenSSLCertificateOptions} will cause it to raise an
        exception.
        """
        with self.assertRaises(TypeError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                raiseMinimumTo=sslverify.TLSVersion.TLSv1_2,
                insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_2,
            )

        self.assertIn("raiseMinimumTo", e.exception.args[0])
        self.assertIn("insecurelyLowerMinimumTo", e.exception.args[0])
        self.assertIn("exclusive", e.exception.args[0])

    def test_tlsProtocolsNoMethodWithAtLeast(self):
        """
        Passing C{raiseMinimumTo} along with C{method} to
        L{sslverify.OpenSSLCertificateOptions} will cause it to raise an
        exception.
        """
        with self.assertRaises(TypeError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                method=SSL.SSLv23_METHOD,
                raiseMinimumTo=sslverify.TLSVersion.TLSv1_2,
            )

        self.assertIn("method", e.exception.args[0])
        self.assertIn("raiseMinimumTo", e.exception.args[0])
        self.assertIn("exclusive", e.exception.args[0])

    def test_tlsProtocolsNoMethodWithMinimum(self):
        """
        Passing C{insecurelyLowerMinimumTo} along with C{method} to
        L{sslverify.OpenSSLCertificateOptions} will cause it to raise an
        exception.
        """
        with self.assertRaises(TypeError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                method=SSL.SSLv23_METHOD,
                insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_2,
            )

        self.assertIn("method", e.exception.args[0])
        self.assertIn("insecurelyLowerMinimumTo", e.exception.args[0])
        self.assertIn("exclusive", e.exception.args[0])

    def test_tlsProtocolsNoMethodWithMaximum(self):
        """
        Passing C{lowerMaximumSecurityTo} along with C{method} to
        L{sslverify.OpenSSLCertificateOptions} will cause it to raise an
        exception.
        """
        with self.assertRaises(TypeError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                method=SSL.TLS_METHOD,
                lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_2,
            )

        self.assertIn("method", e.exception.args[0])
        self.assertIn("lowerMaximumSecurityTo", e.exception.args[0])
        self.assertIn("exclusive", e.exception.args[0])

    def test_verifyCallbackCrashNoRetain(self) -> None:
        """
        Since circular references go through CFFI here, the verification
        callback has to be very careful to avoid creating any circular
        references which OpenSSL will hold on to.  Make sure that the
        TLSMemoryBIOProtocol is no longer referenced after the verification
        callback runs, even if it explodes with a totally unexpected error.
        """
        f = protocol.Factory.forProtocol(protocol.Protocol)
        proto = TLSMemoryBIOProtocol(
            TLSMemoryBIOFactory(
                optionsForClientTLS("just-testing"),
                True,
                f,
            ),
            f.buildProtocol(IPv4Address("TCP", "192.168.1.1", 8123)),
        )
        r = ref(proto)
        # intentional type check failure
        cb = sslverify._verifyCB(proto, True, "just-testing")
        # consistency check: we still retain a reference.
        self.assertIsNot(r(), None)
        # testing internal bug-reporting here, so we intentionally break its
        # type contract and expect some random explosion. Just specifying
        # AttributeError here so if behavior changes and it's something else we
        # get notified in the future.
        cb(None, None, None, 0, None)  # type:ignore
        self.flushLoggedErrors(AttributeError)
        del proto
        gc.collect()
        it = r()
        self.assertIs(it, None)

    def test_tlsVersionRangeInOrder(self):
        """
        Passing out of order TLS versions to C{insecurelyLowerMinimumTo} and
        C{lowerMaximumSecurityTo} will cause it to raise an exception.
        """
        with self.assertRaises(ValueError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_0,
                lowerMaximumSecurityTo=sslverify.TLSVersion.SSLv3,
            )

        self.assertEqual(
            e.exception.args,
            (
                (
                    "insecurelyLowerMinimumTo needs to be lower than "
                    "lowerMaximumSecurityTo"
                ),
            ),
        )

    def test_tlsVersionRangeInOrderAtLeast(self):
        """
        Passing out of order TLS versions to C{raiseMinimumTo} and
        C{lowerMaximumSecurityTo} will cause it to raise an exception.
        """
        with self.assertRaises(ValueError) as e:
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                raiseMinimumTo=sslverify.TLSVersion.TLSv1_0,
                lowerMaximumSecurityTo=sslverify.TLSVersion.SSLv3,
            )

        self.assertEqual(
            e.exception.args,
            (("raiseMinimumTo needs to be lower than " "lowerMaximumSecurityTo"),),
        )

    def test_tlsProtocolsreduceToMaxWithoutMin(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{lowerMaximumSecurityTo} but no C{raiseMinimumTo} or
        C{insecurelyLowerMinimumTo} set, and C{lowerMaximumSecurityTo} is
        below the minimum default, the minimum will be made the new maximum.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            lowerMaximumSecurityTo=sslverify.TLSVersion.SSLv3,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
            | SSL.OP_NO_TLSv1_2
            | opts._OP_NO_TLSv1_3
        )
        self.assertEqual(options, ctx._options & options)

    def test_tlsProtocolsSSLv3Only(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} and C{lowerMaximumSecurityTo} set to
        SSLv3, it will exclude all others.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.SSLv3,
            lowerMaximumSecurityTo=sslverify.TLSVersion.SSLv3,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
            | SSL.OP_NO_TLSv1_2
            | opts._OP_NO_TLSv1_3
        )
        antiOptions = SSL.OP_NO_SSLv3
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(0, ctx._options & antiOptions)

    def test_tlsProtocolsTLSv1Point0Only(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} and C{lowerMaximumSecurityTo} set to v1.0,
        it will exclude all others.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_0,
            lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_0,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1_1
            | SSL.OP_NO_TLSv1_2
            | opts._OP_NO_TLSv1_3
        )
        antiOptions = SSL.OP_NO_TLSv1
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(0, ctx._options & antiOptions)

    def test_tlsProtocolsTLSv1Point1Only(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} and C{lowerMaximumSecurityTo} set to v1.1,
        it will exclude all others.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_1,
            lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_1,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_2
            | opts._OP_NO_TLSv1_3
        )
        antiOptions = SSL.OP_NO_TLSv1_1
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(0, ctx._options & antiOptions)

    def test_tlsProtocolsTLSv1Point2Only(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} and C{lowerMaximumSecurityTo} set to v1.2,
        it will exclude all others.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_2,
            lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_2,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
            | opts._OP_NO_TLSv1_3
        )
        antiOptions = SSL.OP_NO_TLSv1_2
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(0, ctx._options & antiOptions)

    def test_tlsProtocols1_1And1_2(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} set to TLSv1.0 and
        C{lowerMaximumSecurityTo} to TLSv1.2, it will exclude both SSLv2/v3 and
        TLSv1.3.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_0,
            lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_2,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | opts._OP_NO_TLSv1_3
        )
        antiOptions = SSL.OP_NO_TLSv1_2 | SSL.OP_NO_TLSv1_1
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(0, ctx._options & antiOptions)

    def test_tlsProtocolsAtLeastAllSecureTLS(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{raiseMinimumTo} set to TLSv1.2, it will ignore all TLSs below
        1.2 and SSL.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            raiseMinimumTo=sslverify.TLSVersion.TLSv1_2,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
        )
        self.assertEqual(options, ctx._options & options)

    def test_tlsProtocolsAtLeastWillAcceptHigherDefault(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{raiseMinimumTo} set to a value lower than Twisted's default will
        cause it to use the more secure default.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            raiseMinimumTo=sslverify.TLSVersion.SSLv3,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        # Future maintainer warning: this will break if we change our default
        # up, so you should change it to add the relevant OP_NO flags when we
        # do make that change and this test fails.
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
        )
        self.assertEqual(options, ctx._options & options)
        self.assertEqual(opts._defaultMinimumTLSVersion, sslverify.TLSVersion.TLSv1_2)

    def test_tlsProtocolsAllSecureTLS(self):
        """
        When calling L{sslverify.OpenSSLCertificateOptions} with
        C{insecurelyLowerMinimumTo} set to TLSv1.2, it will ignore all TLSs below
        1.2 and SSL.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            insecurelyLowerMinimumTo=sslverify.TLSVersion.TLSv1_2,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        options = (
            SSL.OP_NO_SSLv2
            | SSL.OP_NO_COMPRESSION
            | SSL.OP_CIPHER_SERVER_PREFERENCE
            | SSL.OP_NO_SSLv3
            | SSL.OP_NO_TLSv1
            | SSL.OP_NO_TLSv1_1
        )
        self.assertEqual(options, ctx._options & options)

    def test_dhParams(self):
        """
        If C{dhParams} is set, they are loaded into each new context.
        """

        class FakeDiffieHellmanParameters:
            _dhFile = FilePath(b"dh.params")

        dhParams = FakeDiffieHellmanParameters()
        opts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            dhParameters=dhParams,
        )
        opts._contextFactory = FakeContext
        ctx = opts.getContext()
        self.assertEqual(FakeDiffieHellmanParameters._dhFile.path, ctx._dhFilename)

    def test_abbreviatingDistinguishedNames(self):
        """
        Check that abbreviations used in certificates correctly map to
        complete names.
        """
        self.assertEqual(
            sslverify.DN(CN=b"a", OU=b"hello"),
            sslverify.DistinguishedName(
                commonName=b"a", organizationalUnitName=b"hello"
            ),
        )
        self.assertNotEqual(
            sslverify.DN(CN=b"a", OU=b"hello"),
            sslverify.DN(CN=b"a", OU=b"hello", emailAddress=b"xxx"),
        )
        dn = sslverify.DN(CN=b"abcdefg")
        self.assertRaises(AttributeError, setattr, dn, "Cn", b"x")
        self.assertEqual(dn.CN, dn.commonName)
        dn.CN = b"bcdefga"
        self.assertEqual(dn.CN, dn.commonName)

    def testInspectDistinguishedName(self):
        n = sslverify.DN(
            commonName=b"common name",
            organizationName=b"organization name",
            organizationalUnitName=b"organizational unit name",
            localityName=b"locality name",
            stateOrProvinceName=b"state or province name",
            countryName=b"country name",
            emailAddress=b"email address",
        )
        s = n.inspect()
        for k in [
            "common name",
            "organization name",
            "organizational unit name",
            "locality name",
            "state or province name",
            "country name",
            "email address",
        ]:
            self.assertIn(k, s, f"{k!r} was not in inspect output.")
            self.assertIn(k.title(), s, f"{k!r} was not in inspect output.")

    def testInspectDistinguishedNameWithoutAllFields(self):
        n = sslverify.DN(localityName=b"locality name")
        s = n.inspect()
        for k in [
            "common name",
            "organization name",
            "organizational unit name",
            "state or province name",
            "country name",
            "email address",
        ]:
            self.assertNotIn(k, s, f"{k!r} was in inspect output.")
            self.assertNotIn(k.title(), s, f"{k!r} was in inspect output.")
        self.assertIn("locality name", s)
        self.assertIn("Locality Name", s)

    def test_inspectCertificate(self):
        """
        Test that the C{inspect} method of L{sslverify.Certificate} returns
        a human-readable string containing some basic information about the
        certificate.
        """
        c = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)
        pk = c.getPublicKey()
        keyHash = pk.keyHash()
        # Maintenance Note: the algorithm used to compute the "public key hash"
        # is highly dubious and can differ between underlying versions of
        # OpenSSL (and across versions of Twisted), since it is not actually
        # the hash of the public key by itself.  If we can get the appropriate
        # APIs to get the hash of the key itself out of OpenSSL, then we should
        # be able to make it statically declared inline below again rather than
        # computing it here.
        self.assertEqual(
            c.inspect().split("\n"),
            [
                "Certificate For Subject:",
                "               Common Name: example.twistedmatrix.com",
                "              Country Name: US",
                "             Email Address: nobody@twistedmatrix.com",
                "             Locality Name: Boston",
                "         Organization Name: Twisted Matrix Labs",
                "  Organizational Unit Name: Security",
                "    State Or Province Name: Massachusetts",
                "",
                "Issuer:",
                "               Common Name: example.twistedmatrix.com",
                "              Country Name: US",
                "             Email Address: nobody@twistedmatrix.com",
                "             Locality Name: Boston",
                "         Organization Name: Twisted Matrix Labs",
                "  Organizational Unit Name: Security",
                "    State Or Province Name: Massachusetts",
                "",
                "Serial Number: 12345",
                "Digest: C4:96:11:00:30:C3:EC:EE:A3:55:AA:ED:8C:84:85:18",
                "Public Key with Hash: " + keyHash,
            ],
        )

    def test_representationOfCertificate(self):
        """
        The repr of L{sslverify.Certificate} returns
        a human-readable string containing the subject of the certificate itself, and the subject of the signing CA.
        """
        c = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)
        self.assertEqual(
            repr(c),
            "<Certificate Subject=b'example.twistedmatrix.com' Issuer=b'example.twistedmatrix.com'>",
        )

    def test_representationOfCertificateNoCN(self):
        """
        Repr of a L{sslverify.Certificate} for which both the certificate and the signing CA have no subject, contains the empty string as the subject.
        """
        ubuntuOneGoDaddyPem = textwrap.dedent(
            """\
            -----BEGIN CERTIFICATE-----
            MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
            ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
            RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
            MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
            QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
            b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
            b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
            YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
            AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
            KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
            VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
            SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
            cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
            6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
            MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
            kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
            BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
            BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
            c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
            AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
            BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
            OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
            A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
            0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
            RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
            qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
            U+4=
            -----END CERTIFICATE-----
            """
        )
        c = sslverify.Certificate.loadPEM(ubuntuOneGoDaddyPem)
        self.assertEqual(
            repr(c),
            "<Certificate Subject=b'Go Daddy Secure Certification Authority' Issuer=>",
        )

    def test_publicKeyMatching(self):
        """
        L{PublicKey.matches} returns L{True} for keys from certificates with
        the same key, and L{False} for keys from certificates with different
        keys.
        """
        hostA = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)
        hostB = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)
        peerA = sslverify.Certificate.loadPEM(A_PEER_CERTIFICATE_PEM)

        self.assertTrue(hostA.getPublicKey().matches(hostB.getPublicKey()))
        self.assertFalse(peerA.getPublicKey().matches(hostA.getPublicKey()))

    def test_enablingAndDisablingSessions(self):
        """
        The enableSessions argument sets the session cache mode; it defaults to
        False (at least until https://twistedmatrix.com/trac/ticket/9764 can be
        resolved).
        """
        options = sslverify.OpenSSLCertificateOptions()
        self.assertEqual(options.enableSessions, False)
        ctx = options.getContext()
        self.assertEqual(ctx.get_session_cache_mode(), SSL.SESS_CACHE_OFF)
        options = sslverify.OpenSSLCertificateOptions(enableSessions=True)
        self.assertEqual(options.enableSessions, True)
        ctx = options.getContext()
        self.assertEqual(ctx.get_session_cache_mode(), SSL.SESS_CACHE_SERVER)

    def test_certificateOptionsSerialization(self):
        """
        Test that __setstate__(__getstate__()) round-trips properly.
        """
        firstOpts = sslverify.OpenSSLCertificateOptions(
            privateKey=self.sKey,
            certificate=self.sCert,
            method=SSL.SSLv23_METHOD,
            verify=True,
            caCerts=[self.sCert],
            verifyDepth=2,
            requireCertificate=False,
            verifyOnce=False,
            enableSingleUseKeys=False,
            enableSessions=False,
            fixBrokenPeers=True,
            enableSessionTickets=True,
        )
        context = firstOpts.getContext()
        self.assertIs(context, firstOpts._context)
        self.assertIsNotNone(context)
        state = firstOpts.__getstate__()
        self.assertNotIn("_context", state)

        opts = sslverify.OpenSSLCertificateOptions()
        opts.__setstate__(state)
        self.assertEqual(opts.privateKey, self.sKey)
        self.assertEqual(opts.certificate, self.sCert)
        self.assertEqual(opts.method, SSL.SSLv23_METHOD)
        self.assertTrue(opts.verify)
        self.assertEqual(opts.caCerts, [self.sCert])
        self.assertEqual(opts.verifyDepth, 2)
        self.assertFalse(opts.requireCertificate)
        self.assertFalse(opts.verifyOnce)
        self.assertFalse(opts.enableSingleUseKeys)
        self.assertFalse(opts.enableSessions)
        self.assertTrue(opts.fixBrokenPeers)
        self.assertTrue(opts.enableSessionTickets)

    test_certificateOptionsSerialization.suppress = [  # type: ignore[attr-defined]
        util.suppress(
            category=DeprecationWarning,
            message=r"twisted\.internet\._sslverify\.*__[gs]etstate__",
        )
    ]

    def test_certificateOptionsSessionTickets(self):
        """
        Enabling session tickets should not set the OP_NO_TICKET option.
        """
        opts = sslverify.OpenSSLCertificateOptions(enableSessionTickets=True)
        ctx = opts._makeContext(skipCiphers=True)
        self.assertEqual(0, ctx.set_options(0) & 0x00004000)

    def test_certificateOptionsSessionTicketsDisabled(self):
        """
        Enabling session tickets should set the OP_NO_TICKET option.
        """
        opts = sslverify.OpenSSLCertificateOptions(enableSessionTickets=False)
        ctx = opts._makeContext(skipCiphers=True)
        self.assertEqual(0x00004000, ctx.set_options(0) & 0x00004000)

    def test_allowedAnonymousClientConnection(self):
        """
        Check that anonymous connections are allowed when certificates aren't
        required on the server.
        """
        onData = defer.Deferred()
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey, certificate=self.sCert, requireCertificate=False
            ),
            sslverify.OpenSSLCertificateOptions(requireCertificate=False),
            onData=onData,
        )

        return onData.addCallback(
            lambda result: self.assertEqual(result, WritingProtocol.byte)
        )

    def test_refusedAnonymousClientConnection(self):
        """
        Check that anonymous connections are refused when certificates are
        required on the server.
        """
        onServerLost = defer.Deferred()
        onClientLost = defer.Deferred()
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                verify=True,
                caCerts=[self.sCert],
                requireCertificate=True,
            ),
            sslverify.OpenSSLCertificateOptions(requireCertificate=False),
            onServerLost=onServerLost,
            onClientLost=onClientLost,
        )

        d = defer.DeferredList([onClientLost, onServerLost], consumeErrors=True)

        def afterLost(result):
            ((cSuccess, cResult), (sSuccess, sResult)) = result
            self.assertFalse(cSuccess)
            self.assertFalse(sSuccess)
            # Win32 fails to report the SSL Error, and report a connection lost
            # instead: there is a race condition so that's not totally
            # surprising (see ticket #2877 in the tracker)
            self.assertIsInstance(cResult.value, (SSL.Error, ConnectionLost))
            self.assertIsInstance(sResult.value, SSL.Error)

        return d.addCallback(afterLost)

    def test_failedCertificateVerification(self):
        """
        Check that connecting with a certificate not accepted by the server CA
        fails.
        """
        onServerLost = defer.Deferred()
        onClientLost = defer.Deferred()
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                verify=False,
                requireCertificate=False,
            ),
            sslverify.OpenSSLCertificateOptions(
                verify=True, requireCertificate=False, caCerts=[self.cCert]
            ),
            onServerLost=onServerLost,
            onClientLost=onClientLost,
        )

        d = defer.DeferredList([onClientLost, onServerLost], consumeErrors=True)

        def afterLost(result):
            ((cSuccess, cResult), (sSuccess, sResult)) = result
            self.assertFalse(cSuccess)
            self.assertFalse(sSuccess)

        return d.addCallback(afterLost)

    def test_successfulCertificateVerification(self):
        """
        Test a successful connection with client certificate validation on
        server side.
        """
        onData = defer.Deferred()
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                verify=False,
                requireCertificate=False,
            ),
            sslverify.OpenSSLCertificateOptions(
                verify=True, requireCertificate=True, caCerts=[self.sCert]
            ),
            onData=onData,
        )

        return onData.addCallback(
            lambda result: self.assertEqual(result, WritingProtocol.byte)
        )

    def test_successfulSymmetricSelfSignedCertificateVerification(self):
        """
        Test a successful connection with validation on both server and client
        sides.
        """
        onData = defer.Deferred()
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                verify=True,
                requireCertificate=True,
                caCerts=[self.cCert],
            ),
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.cKey,
                certificate=self.cCert,
                verify=True,
                requireCertificate=True,
                caCerts=[self.sCert],
            ),
            onData=onData,
        )

        return onData.addCallback(
            lambda result: self.assertEqual(result, WritingProtocol.byte)
        )

    def test_verification(self):
        """
        Check certificates verification building custom certificates data.
        """
        clientDN = sslverify.DistinguishedName(commonName="client")
        clientKey = sslverify.KeyPair.generate()
        clientCertReq = clientKey.certificateRequest(clientDN)

        serverDN = sslverify.DistinguishedName(commonName="server")
        serverKey = sslverify.KeyPair.generate()
        serverCertReq = serverKey.certificateRequest(serverDN)

        clientSelfCertReq = clientKey.certificateRequest(clientDN)
        clientSelfCertData = clientKey.signCertificateRequest(
            clientDN, clientSelfCertReq, lambda dn: True, 132
        )
        clientSelfCert = clientKey.newCertificate(clientSelfCertData)

        serverSelfCertReq = serverKey.certificateRequest(serverDN)
        serverSelfCertData = serverKey.signCertificateRequest(
            serverDN, serverSelfCertReq, lambda dn: True, 516
        )
        serverSelfCert = serverKey.newCertificate(serverSelfCertData)

        clientCertData = serverKey.signCertificateRequest(
            serverDN, clientCertReq, lambda dn: True, 7
        )
        clientCert = clientKey.newCertificate(clientCertData)

        serverCertData = clientKey.signCertificateRequest(
            clientDN, serverCertReq, lambda dn: True, 42
        )
        serverCert = serverKey.newCertificate(serverCertData)

        onData = defer.Deferred()

        serverOpts = serverCert.options(serverSelfCert)
        clientOpts = clientCert.options(clientSelfCert)

        self.loopback(serverOpts, clientOpts, onData=onData)

        return onData.addCallback(
            lambda result: self.assertEqual(result, WritingProtocol.byte)
        )


class OpenSSLOptionsECDHIntegrationTests(OpenSSLOptionsTestsMixin, TestCase):
    """
    ECDH-related integration tests for L{OpenSSLOptions}.
    """

    def test_ellipticCurveDiffieHellman(self):
        """
        Connections use ECDH when OpenSSL supports it.
        """
        if not get_elliptic_curves():
            raise SkipTest("OpenSSL does not support ECDH.")

        onData = defer.Deferred()
        # TLS 1.3 cipher suites do not specify the key exchange
        # mechanism:
        # https://wiki.openssl.org/index.php/TLS1.3#Differences_with_TLS1.2_and_below
        #
        # and OpenSSL only supports ECHDE groups with TLS 1.3:
        # https://wiki.openssl.org/index.php/TLS1.3#Groups
        #
        # so TLS 1.3 implies ECDHE.  Force this test to use TLS 1.2 to
        # ensure ECDH is selected when it might not be.
        self.loopback(
            sslverify.OpenSSLCertificateOptions(
                privateKey=self.sKey,
                certificate=self.sCert,
                requireCertificate=False,
                lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_2,
            ),
            sslverify.OpenSSLCertificateOptions(
                requireCertificate=False,
                lowerMaximumSecurityTo=sslverify.TLSVersion.TLSv1_2,
            ),
            onData=onData,
        )

        @onData.addCallback
        def assertECDH(_):
            self.assertEqual(len(self.clientConn.factory.protocols), 1)
            [clientProtocol] = self.clientConn.factory.protocols
            cipher = clientProtocol.getHandle().get_cipher_name()
            self.assertIn("ECDH", cipher)

        return onData


class DeprecationTests(SynchronousTestCase):
    """
    Tests for deprecation of L{sslverify.OpenSSLCertificateOptions}'s support
    of the pickle protocol.
    """

    if skipSSL:
        skip = skipSSL

    def test_getstateDeprecation(self):
        """
        L{sslverify.OpenSSLCertificateOptions.__getstate__} is deprecated.
        """
        self.callDeprecated(
            (Version("Twisted", 15, 0, 0), "a real persistence system"),
            sslverify.OpenSSLCertificateOptions().__getstate__,
        )

    def test_setstateDeprecation(self):
        """
        L{sslverify.OpenSSLCertificateOptions.__setstate__} is deprecated.
        """
        self.callDeprecated(
            (Version("Twisted", 15, 0, 0), "a real persistence system"),
            sslverify.OpenSSLCertificateOptions().__setstate__,
            {},
        )


class TrustRootTests(TestCase):
    """
    Tests for L{sslverify.OpenSSLCertificateOptions}' C{trustRoot} argument,
    L{sslverify.platformTrust}, and their interactions.
    """

    if skipSSL:
        skip = skipSSL

    def setUp(self):
        """
        Patch L{sslverify._ChooseDiffieHellmanEllipticCurve}.
        """
        self.patch(
            sslverify,
            "_ChooseDiffieHellmanEllipticCurve",
            FakeChooseDiffieHellmanEllipticCurve,
        )

    def test_caCertsPlatformDefaults(self):
        """
        Specifying a C{trustRoot} of L{sslverify.OpenSSLDefaultPaths} when
        initializing L{sslverify.OpenSSLCertificateOptions} loads the
        platform-provided trusted certificates via C{set_default_verify_paths}.
        """
        opts = sslverify.OpenSSLCertificateOptions(
            trustRoot=sslverify.OpenSSLDefaultPaths(),
        )
        fc = FakeContext(SSL.TLSv1_METHOD)
        opts._contextFactory = lambda method: fc
        opts.getContext()
        self.assertTrue(fc._defaultVerifyPathsSet)

    def test_trustRootPlatformRejectsUntrustedCA(self):
        """
        Specifying a C{trustRoot} of L{platformTrust} when initializing
        L{sslverify.OpenSSLCertificateOptions} causes certificates issued by a
        newly created CA to be rejected by an SSL connection using these
        options.

        Note that this test should I{always} pass, even on platforms where the
        CA certificates are not installed, as long as L{platformTrust} rejects
        completely invalid / unknown root CA certificates.  This is simply a
        smoke test to make sure that verification is happening at all.
        """
        caSelfCert, serverCert = certificatesForAuthorityAndServer()
        chainedCert = pathContainingDumpOf(self, serverCert, caSelfCert)
        privateKey = pathContainingDumpOf(self, serverCert.privateKey)

        sProto, cProto, sWrapped, cWrapped, pump = loopbackTLSConnection(
            trustRoot=platformTrust(),
            privateKeyFile=privateKey,
            chainedCertFile=chainedCert,
        )
        # No data was received.
        self.assertEqual(cWrapped.data, b"")

        # It was an L{SSL.Error}.
        self.assertEqual(cWrapped.lostReason.type, SSL.Error)

        # Some combination of OpenSSL and PyOpenSSL is bad at reporting errors.
        err = cWrapped.lostReason.value
        self.assertEqual(err.args[0][0][2], "tlsv1 alert unknown ca")

    def test_trustRootSpecificCertificate(self):
        """
        Specifying a L{Certificate} object for L{trustRoot} will result in that
        certificate being the only trust root for a client.
        """
        caCert, serverCert = certificatesForAuthorityAndServer()
        otherCa, otherServer = certificatesForAuthorityAndServer()
        sProto, cProto, sWrapped, cWrapped, pump = loopbackTLSConnection(
            trustRoot=caCert,
            privateKeyFile=pathContainingDumpOf(self, serverCert.privateKey),
            chainedCertFile=pathContainingDumpOf(self, serverCert),
        )
        pump.flush()
        self.assertIsNone(cWrapped.lostReason)
        self.assertEqual(cWrapped.data, sWrapped.greeting)


@dataclass
class ServiceIdentitySetup:
    clientProtocol: IProtocol
    serverProtocol: IProtocol
    clientWrappedProtocol: IProtocol
    serverWrappedProtocol: IProtocol
    pump: IOPump
    authority: TestingAuthority

    def __iter__(self) -> Iterable[object]:
        return iter(
            (
                self.clientProtocol,
                self.serverProtocol,
                self.clientWrappedProtocol,
                self.serverWrappedProtocol,
                self.pump,
            )
        )


class ServiceIdentityTests(SynchronousTestCase):
    """
    Tests for the verification of the peer's service's identity via the
    C{hostname} argument to L{sslverify.OpenSSLCertificateOptions}.
    """

    if skipSSL:
        skip = skipSSL

    def serviceIdentitySetup(
        self,
        clientHostname,
        serverHostname,
        *,
        serverNameCallback=None,
        validCertificate=True,
        clientPresentsCertificate=False,
        validClientCertificate=True,
        serverVerifies=False,
        fakePlatformTrust=False,
        useDefaultTrust=False,
        clientSkipSNI=False,
        immediately=True,
    ):
        """
        Connect a server and a client.

        @param clientHostname: The I{client's idea} of the server's hostname;
            passed as the C{hostname} to the
            L{sslverify.OpenSSLCertificateOptions} instance.
        @type clientHostname: L{unicode}

        @param serverHostname: The I{server's own idea} of the server's
            hostname; present in the certificate presented by the server.
        @type serverHostname: L{unicode}

        @param validCertificate: Is the server's certificate valid?  L{True} if
            so, L{False} otherwise.
        @type validCertificate: L{bool}

        @param clientPresentsCertificate: Should the client present a
            certificate to the server?  Defaults to 'no'.
        @type clientPresentsCertificate: L{bool}

        @param validClientCertificate: If the client presents a certificate,
            should it actually be a valid one, i.e. signed by the same CA that
            the server is checking?  Defaults to 'yes'.
        @type validClientCertificate: L{bool}

        @param serverVerifies: Should the server verify the client's
            certificate?  Defaults to 'no'.
        @type serverVerifies: L{bool}

        @param fakePlatformTrust: Should we fake the platformTrust to be the
            same as our fake server certificate authority, so that we can test
            it's being used?  Defaults to 'no' and we just pass platform trust.
        @type fakePlatformTrust: L{bool}

        @param useDefaultTrust: Should we avoid passing the C{trustRoot} to
            L{ssl.optionsForClientTLS}?  Defaults to 'no'.
        @type useDefaultTrust: L{bool}

        @return: the client TLS protocol, the client wrapped protocol,
            the server TLS protocol, the server wrapped protocol and
            an L{IOPump} which, when its C{pump} and C{flush} methods are
            called, will move data between the created client and server
            protocol instances
        @rtype: L{ServiceIdentitySetup}
        """
        clientAuthority = TestingAuthority.create()
        serverAuthority = TestingAuthority.create()
        untrustedAuthority = TestingAuthority.create()
        serverCA = serverAuthority.authorityCertificate()
        serverCert = serverAuthority.serverCertificate("Valid Cert", [serverHostname])
        other = {}
        passClientCert = None
        clientCA = clientAuthority.authorityCertificate()
        clientCert = clientAuthority.serverCertificate("Client Cert", ["client"])
        if serverVerifies:
            other.update(trustRoot=clientCA)

        if clientPresentsCertificate:
            if validClientCertificate:
                passClientCert = clientCert
            else:
                passClientCert = untrustedAuthority.serverCertificate(
                    "Client Cert", ["client"]
                )

        if not validCertificate:
            serverCert = untrustedAuthority.serverCertificate(
                "Invalid Cert", [serverHostname]
            )

        serverOpts = sslverify.OpenSSLCertificateOptions(
            privateKey=serverCert.privateKey.original,
            certificate=serverCert.original,
            contextForServerName=serverNameCallback,
            **other,
        )

        signature = {"hostname": clientHostname}
        if passClientCert:
            signature.update(clientCertificate=passClientCert)
        if not useDefaultTrust:
            signature.update(trustRoot=serverCA)
        if fakePlatformTrust:
            self.patch(sslverify, "platformTrust", lambda: serverCA)
        if clientSkipSNI:
            signature.update(sendServerName=False)

        clientOpts = sslverify.optionsForClientTLS(**signature)

        class GreetingServer(protocol.Protocol):
            greeting = b"greetings!"
            lostReason = None
            data = b""

            def connectionMade(self):
                self.transport.write(self.greeting)

            def dataReceived(self, data):
                self.data += data

            def connectionLost(self, reason):
                self.lostReason = reason

        class GreetingClient(protocol.Protocol):
            greeting = b"cheerio!"
            data = b""
            lostReason = None

            def connectionMade(self):
                self.transport.write(self.greeting)

            def dataReceived(self, data):
                self.data += data

            def connectionLost(self, reason):
                self.lostReason = reason

        serverWrappedProto = GreetingServer()
        clientWrappedProto = GreetingClient()

        clientFactory = protocol.Factory()
        clientFactory.protocol = lambda: clientWrappedProto
        serverFactory = protocol.Factory()
        serverFactory.protocol = lambda: serverWrappedProto

        self.serverOpts = serverOpts
        self.clientOpts = clientOpts

        clock = Clock()
        clientTLSFactory = TLSMemoryBIOFactory(
            clientOpts, isClient=True, wrappedFactory=clientFactory, clock=clock
        )
        serverTLSFactory = TLSMemoryBIOFactory(
            serverOpts, isClient=False, wrappedFactory=serverFactory, clock=clock
        )

        cProto, sProto, pump = connectedServerAndClient(
            lambda: serverTLSFactory.buildProtocol(None),
            lambda: clientTLSFactory.buildProtocol(None),
            clock=clock,
            greet=immediately,
        )
        if immediately:
            pump.flush()

        return ServiceIdentitySetup(
            cProto,
            sProto,
            clientWrappedProto,
            serverWrappedProto,
            pump,
            serverAuthority,
        )

    def test_invalidHostname(self):
        """
        When a certificate containing an invalid hostname is received from the
        server, the connection is immediately dropped.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "wrong-host.example.com",
            "correct-host.example.com",
        )
        self.assertEqual(cWrapped.data, b"")
        self.assertEqual(sWrapped.data, b"")

        cErr = cWrapped.lostReason.value
        sErr = sWrapped.lostReason.value

        self.assertIsInstance(cErr, VerificationError)
        self.assertIsInstance(sErr, ConnectionClosed)

    def test_sniContextSwitch(self) -> None:
        """
        The C{contextForServerName} argument to L{sslverify.OpenSSLCertificateOptions}
        returns a new L{SSL.Context} that will be used for the TLS connection
        in response to the server name indication being sent.
        """

        servers = []

        def switchToCorrect(servername: bytes | None) -> SSL.Context:
            servers.append(servername)
            options: sslverify.OpenSSLCertificateOptions = validCert.options()
            return options.getContext()

        conf = self.serviceIdentitySetup(
            "correct-host.example.com",
            "wrong-host.example.com",
            serverNameCallback=switchToCorrect,
            immediately=False,
        )
        validCert = conf.authority.serverCertificate(
            "Correct Cert", ["correct-host.example.com"]
        )
        cProto, sProto, cWrapped, sWrapped, pump = conf
        pump.flush()
        self.assertEqual(servers, [b"correct-host.example.com"])
        self.assertEqual(cWrapped.data, b"greetings!")

        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)

    def test_sniCallbackError(self) -> None:
        """
        The C{contextForServerName} argument to
        L{sslverify.OpenSSLCertificateOptions} can raise an exception which
        will log an error and immediately drop the connection.
        """

        def brokenCallback(servername: bytes | None) -> SSL.Context:
            raise RuntimeError("here's an error")

        conf = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            serverNameCallback=brokenCallback,
        )
        cProto, sProto, cWrapped, sWrapped, pump = conf
        logged = self.flushLoggedErrors(RuntimeError)
        self.assertEqual(len(logged), 1)
        self.assertEqual(str(logged[0].value), "here's an error")
        self.assertEqual(cWrapped.data, b"")

        cErr = cWrapped.lostReason.value
        sErr = sWrapped.lostReason.value

        self.assertIsInstance(cErr, ConnectionLost)
        self.assertIsInstance(sErr, ConnectionLost)

    def test_noSNICallback(self) -> None:
        """
        When SNI is not sent by the client, the server name callback will be
        invoked with C{None}.
        """
        sent: list[bytes] = []
        conf = self.serviceIdentitySetup(
            "correct-host.example.com",
            "correct-host.example.com",
            serverNameCallback=sent.append,
            immediately=False,
            clientSkipSNI=True,
        )
        cProto, sProto, cWrapped, sWrapped, pump = conf
        pump.flush()
        self.assertEqual(cWrapped.data, b"greetings!")
        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)
        self.assertEqual(sent, [None])

    def test_validHostname(self):
        """
        Whenever a valid certificate containing a valid hostname is received,
        connection proceeds normally.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
        )
        self.assertEqual(cWrapped.data, b"greetings!")

        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)

    def test_validHostnameInvalidCertificate(self):
        """
        When an invalid certificate containing a perfectly valid hostname is
        received, the connection is aborted with an OpenSSL error.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            validCertificate=False,
        )

        self.assertEqual(cWrapped.data, b"")
        self.assertEqual(sWrapped.data, b"")

        cErr = cWrapped.lostReason.value
        sErr = sWrapped.lostReason.value

        self.assertIsInstance(cErr, SSL.Error)
        self.assertIsInstance(sErr, SSL.Error)

    def test_realCAsBetterNotSignOurBogusTestCerts(self):
        """
        If we use the default trust from the platform, our dinky certificate
        should I{really} fail.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            validCertificate=False,
            useDefaultTrust=True,
        )

        self.assertEqual(cWrapped.data, b"")
        self.assertEqual(sWrapped.data, b"")

        cErr = cWrapped.lostReason.value
        sErr = sWrapped.lostReason.value

        self.assertIsInstance(cErr, SSL.Error)
        self.assertIsInstance(sErr, SSL.Error)

    def test_butIfTheyDidItWouldWork(self):
        """
        L{ssl.optionsForClientTLS} should be using L{ssl.platformTrust} by
        default, so if we fake that out then it should trust ourselves again.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            useDefaultTrust=True,
            fakePlatformTrust=True,
        )
        self.assertEqual(cWrapped.data, b"greetings!")

        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)

    def test_clientPresentsCertificate(self):
        """
        When the server verifies and the client presents a valid certificate
        for that verification by passing it to
        L{sslverify.optionsForClientTLS}, communication proceeds.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            validCertificate=True,
            serverVerifies=True,
            clientPresentsCertificate=True,
        )

        self.assertEqual(cWrapped.data, b"greetings!")

        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)

    def test_clientPresentsBadCertificate(self):
        """
        When the server verifies and the client presents an invalid certificate
        for that verification by passing it to
        L{sslverify.optionsForClientTLS}, the connection cannot be established
        with an SSL error.
        """
        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            validCertificate=True,
            serverVerifies=True,
            validClientCertificate=False,
            clientPresentsCertificate=True,
        )

        self.assertEqual(cWrapped.data, b"")

        cErr = cWrapped.lostReason.value
        sErr = sWrapped.lostReason.value

        self.assertIsInstance(cErr, SSL.Error)
        self.assertIsInstance(sErr, SSL.Error)

    def test_hostnameIsIndicated(self):
        """
        Specifying the C{hostname} argument to L{CertificateOptions} also sets
        the U{Server Name Extension
        <https://en.wikipedia.org/wiki/Server_Name_Indication>} TLS indication
        field to the correct value.
        """
        names = []

        def serverNameCallback(name):
            names.append(name.decode("ascii"))

        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            "valid.example.com",
            "valid.example.com",
            serverNameCallback=serverNameCallback,
        )
        self.assertEqual(names, ["valid.example.com"])

    def test_hostnameEncoding(self):
        """
        Hostnames are encoded as IDNA.
        """
        names = []
        hello = "h\N{LATIN SMALL LETTER A WITH ACUTE}llo.example.com"

        def serverNameCallback(name):
            serverIDNA = _idnaText(name)
            names.append(serverIDNA)

        cProto, sProto, cWrapped, sWrapped, pump = self.serviceIdentitySetup(
            hello, hello, serverNameCallback=serverNameCallback
        )
        self.assertEqual(names, [hello])
        self.assertEqual(cWrapped.data, b"greetings!")

        cErr = cWrapped.lostReason
        sErr = sWrapped.lostReason
        self.assertIsNone(cErr)
        self.assertIsNone(sErr)


def negotiateProtocol(
    serverProtocols: list[bytes],
    clientProtocols: list[bytes],
    viaFactory: bool = False,
    clientHostname: str | None = None,
) -> tuple[bytes | None, Failure | None]:
    """
    Create the TLS connection and negotiate a next protocol.

    @param serverProtocols: The protocols the server is willing to negotiate.

    @param clientProtocols: The protocols the client is willing to negotiate.

    @param viaFactory: whether to supply the client protocols or the server
        protocols via the protocol factory rather than via the context factory.

    @return: A L{tuple} of the negotiated protocol and the reason the
        connection was lost.
    """
    caCertificate, serverCertificate = certificatesForAuthorityAndServer()
    trustRoot = sslverify.OpenSSLCertificateAuthorities([caCertificate.original])

    sProto, cProto, sWrapped, cWrapped, pump = loopbackTLSConnectionInMemory(
        trustRoot=trustRoot,
        privateKey=serverCertificate.privateKey.original,
        serverCertificate=serverCertificate.original,
        clientProtocols=clientProtocols,
        serverProtocols=serverProtocols,
        viaFactory=viaFactory,
        clientOptionsHost=clientHostname,
    )
    pump.flush()

    return (cProto.negotiatedProtocol, cWrapped.lostReason)


class ALPNTests(TestCase):
    """
    ALPN protocol selection.

    These tests only run on platforms that have a PyOpenSSL version >= 0.15,
    and OpenSSL version 1.0.2 or later.
    """

    if skipSSL:
        skip = skipSSL

    def negotiateProtocol(
        self,
        serverProtocols: list[bytes],
        clientProtocols: list[bytes],
        viaFactory: bool = False,
    ) -> tuple[bytes | None, Failure | None]:
        return negotiateProtocol(serverProtocols, clientProtocols, viaFactory)

    def test_nextProtocolMechanismsALPNIsSupported(self) -> None:
        """
        When ALPN is available on a platform, protocolNegotiationMechanisms
        includes ALPN in the suported protocols.
        """
        supportedProtocols = sslverify.protocolNegotiationMechanisms()
        self.assertTrue(sslverify.ProtocolNegotiationSupport.ALPN in supportedProtocols)

    def test_connectionCreatorNegotiation(self) -> None:
        protocols = [b"a", b"b"]
        negotiatedProtocol, lostReason = self.negotiateProtocol(
            clientProtocols=protocols,
            serverProtocols=protocols,
        )
        self.assertIsNone(lostReason)
        self.assertEqual(negotiatedProtocol, b"a")

    def test_factoryNegotiation(self) -> None:
        protocols = [b"a", b"b"]
        negotiatedProtocol, lostReason = self.negotiateProtocol(
            clientProtocols=protocols,
            serverProtocols=protocols,
            viaFactory=True,
        )
        self.assertIsNone(lostReason)
        self.assertEqual(negotiatedProtocol, b"a")

    def test_negotiateEmpty(self) -> None:
        """
        When negotiating with an empty acceptable protocols list, no protocol
        is assigned but the connection is not lost.
        """
        negotiatedProtocol, lostReason = self.negotiateProtocol(
            serverProtocols=[], clientProtocols=[]
        )
        self.assertIs(negotiatedProtocol, None)
        self.assertIs(lostReason, None)

    def test_negotiateNoOverlap(self) -> None:
        """
        When negotiating with an empty acceptable protocols list, no protocol
        is assigned but the connection is not lost.
        """
        negotiatedProtocol, lostReason = self.negotiateProtocol(
            serverProtocols=[b"server-only"], clientProtocols=[b"client-only"]
        )
        self.assertIs(negotiatedProtocol, None)
        self.assertIsNot(lostReason, None)


class ALPNOptionsForClientTLSTests(ALPNTests):
    def negotiateProtocol(
        self,
        serverProtocols: list[bytes],
        clientProtocols: list[bytes],
        viaFactory: bool = False,
    ) -> tuple[bytes | None, Failure | None]:
        return negotiateProtocol(
            serverProtocols, clientProtocols, viaFactory, clientHostname="example.com"
        )


class _NotSSLTransport:
    def getHandle(self):
        return self


class _MaybeSSLTransport:
    def getHandle(self):
        return self

    def get_peer_certificate(self):
        return None

    def get_host_certificate(self):
        return None


class _ActualSSLTransport:
    def getHandle(self):
        return self

    def get_host_certificate(self):
        return sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM).original

    def get_peer_certificate(self):
        return sslverify.Certificate.loadPEM(A_PEER_CERTIFICATE_PEM).original


class ConstructorsTests(TestCase):
    if skipSSL:
        skip = skipSSL

    def test_peerFromNonSSLTransport(self):
        """
        Verify that peerFromTransport raises an exception if the transport
        passed is not actually an SSL transport.
        """
        x = self.assertRaises(
            CertificateError,
            sslverify.Certificate.peerFromTransport,
            _NotSSLTransport(),
        )
        self.assertTrue(str(x).startswith("non-TLS"))

    def test_peerFromBlankSSLTransport(self):
        """
        Verify that peerFromTransport raises an exception if the transport
        passed is an SSL transport, but doesn't have a peer certificate.
        """
        x = self.assertRaises(
            CertificateError,
            sslverify.Certificate.peerFromTransport,
            _MaybeSSLTransport(),
        )
        self.assertTrue(str(x).startswith("TLS"))

    def test_hostFromNonSSLTransport(self):
        """
        Verify that hostFromTransport raises an exception if the transport
        passed is not actually an SSL transport.
        """
        x = self.assertRaises(
            CertificateError,
            sslverify.Certificate.hostFromTransport,
            _NotSSLTransport(),
        )
        self.assertTrue(str(x).startswith("non-TLS"))

    def test_hostFromBlankSSLTransport(self):
        """
        Verify that hostFromTransport raises an exception if the transport
        passed is an SSL transport, but doesn't have a host certificate.
        """
        x = self.assertRaises(
            CertificateError,
            sslverify.Certificate.hostFromTransport,
            _MaybeSSLTransport(),
        )
        self.assertTrue(str(x).startswith("TLS"))

    def test_hostFromSSLTransport(self):
        """
        Verify that hostFromTransport successfully creates the correct
        certificate if passed a valid SSL transport.
        """
        self.assertEqual(
            sslverify.Certificate.hostFromTransport(
                _ActualSSLTransport()
            ).serialNumber(),
            12345,
        )

    def test_peerFromSSLTransport(self):
        """
        Verify that peerFromTransport successfully creates the correct
        certificate if passed a valid SSL transport.
        """
        self.assertEqual(
            sslverify.Certificate.peerFromTransport(
                _ActualSSLTransport()
            ).serialNumber(),
            12346,
        )


class MultipleCertificateTrustRootTests(TestCase):
    """
    Test the behavior of the trustRootFromCertificates() API call.
    """

    if skipSSL:
        skip = skipSSL

    def test_trustRootFromCertificatesPrivatePublic(self):
        """
        L{trustRootFromCertificates} accepts either a L{sslverify.Certificate}
        or a L{sslverify.PrivateCertificate} instance.
        """
        privateCert = sslverify.PrivateCertificate.loadPEM(A_KEYPAIR)
        cert = sslverify.Certificate.loadPEM(A_HOST_CERTIFICATE_PEM)

        mt = sslverify.trustRootFromCertificates([privateCert, cert])

        # Verify that the returned object acts correctly when used as a
        # trustRoot= param to optionsForClientTLS.
        sProto, cProto, sWrap, cWrap, pump = loopbackTLSConnectionInMemory(
            trustRoot=mt,
            privateKey=privateCert.privateKey.original,
            serverCertificate=privateCert.original,
        )

        # This connection should succeed
        self.assertEqual(cWrap.data, b"greetings!")
        self.assertIsNone(cWrap.lostReason)

    def test_trustRootSelfSignedServerCertificate(self):
        """
        L{trustRootFromCertificates} called with a single self-signed
        certificate will cause L{optionsForClientTLS} to accept client
        connections to a server with that certificate.
        """
        key, cert = makeCertificate(O=b"Server Test Certificate", CN=b"server")
        selfSigned = sslverify.PrivateCertificate.fromCertificateAndKeyPair(
            sslverify.Certificate(cert),
            sslverify.KeyPair(key),
        )

        trust = sslverify.trustRootFromCertificates([selfSigned])

        # Since we trust this exact certificate, connections to this server
        # should succeed.
        sProto, cProto, sWrap, cWrap, pump = loopbackTLSConnectionInMemory(
            trustRoot=trust,
            privateKey=selfSigned.privateKey.original,
            serverCertificate=selfSigned.original,
        )
        self.assertEqual(cWrap.data, b"greetings!")
        self.assertIsNone(cWrap.lostReason)

    def test_trustRootCertificateAuthorityTrustsConnection(self):
        """
        L{trustRootFromCertificates} called with certificate A will cause
        L{optionsForClientTLS} to accept client connections to a server with
        certificate B where B is signed by A.
        """
        caCert, serverCert = certificatesForAuthorityAndServer()

        trust = sslverify.trustRootFromCertificates([caCert])

        # Since we've listed the CA's certificate as a trusted cert, a
        # connection to the server certificate it signed should succeed.
        sProto, cProto, sWrap, cWrap, pump = loopbackTLSConnectionInMemory(
            trustRoot=trust,
            privateKey=serverCert.privateKey.original,
            serverCertificate=serverCert.original,
        )
        self.assertEqual(cWrap.data, b"greetings!")
        self.assertIsNone(cWrap.lostReason)

    def test_trustRootFromCertificatesUntrusted(self):
        """
        L{trustRootFromCertificates} called with certificate A will cause
        L{optionsForClientTLS} to disallow any connections to a server with
        certificate B where B is not signed by A.
        """
        key, cert = makeCertificate(O=b"Server Test Certificate", CN=b"server")
        serverCert = sslverify.PrivateCertificate.fromCertificateAndKeyPair(
            sslverify.Certificate(cert),
            sslverify.KeyPair(key),
        )
        untrustedCert = sslverify.Certificate(
            makeCertificate(O=b"CA Test Certificate", CN=b"unknown CA")[1]
        )

        trust = sslverify.trustRootFromCertificates([untrustedCert])

        # Since we only trust 'untrustedCert' which has not signed our
        # server's cert, we should reject this connection
        sProto, cProto, sWrap, cWrap, pump = loopbackTLSConnectionInMemory(
            trustRoot=trust,
            privateKey=serverCert.privateKey.original,
            serverCertificate=serverCert.original,
        )

        # This connection should fail, so no data was received.
        self.assertEqual(cWrap.data, b"")

        # It was an L{SSL.Error}.
        self.assertEqual(cWrap.lostReason.type, SSL.Error)

        # Some combination of OpenSSL and PyOpenSSL is bad at reporting errors.
        err = cWrap.lostReason.value
        self.assertEqual(err.args[0][0][2], "tlsv1 alert unknown ca")

    def test_trustRootFromCertificatesOpenSSLObjects(self):
        """
        L{trustRootFromCertificates} rejects any L{OpenSSL.crypto.X509}
        instances in the list passed to it.
        """
        private = sslverify.PrivateCertificate.loadPEM(A_KEYPAIR)
        certX509 = private.original

        exception = self.assertRaises(
            TypeError,
            sslverify.trustRootFromCertificates,
            [certX509],
        )
        self.assertEqual(
            "certificates items must be twisted.internet.ssl.CertBase " "instances",
            exception.args[0],
        )


class OpenSSLCipherTests(TestCase):
    """
    Tests for twisted.internet._sslverify.OpenSSLCipher.
    """

    if skipSSL:
        skip = skipSSL

    cipherName = "CIPHER-STRING"

    def test_constructorSetsFullName(self):
        """
        The first argument passed to the constructor becomes the full name.
        """
        self.assertEqual(
            self.cipherName, sslverify.OpenSSLCipher(self.cipherName).fullName
        )

    def test_repr(self):
        """
        C{repr(cipher)} returns a valid constructor call.
        """
        cipher = sslverify.OpenSSLCipher(self.cipherName)
        self.assertEqual(
            cipher, eval(repr(cipher), {"OpenSSLCipher": sslverify.OpenSSLCipher})
        )

    def test_eqSameClass(self):
        """
        Equal type and C{fullName} means that the objects are equal.
        """
        cipher1 = sslverify.OpenSSLCipher(self.cipherName)
        cipher2 = sslverify.OpenSSLCipher(self.cipherName)
        self.assertEqual(cipher1, cipher2)

    def test_eqSameNameDifferentType(self):
        """
        If ciphers have the same name but different types, they're still
        different.
        """

        class DifferentCipher:
            fullName = self.cipherName

        self.assertNotEqual(
            sslverify.OpenSSLCipher(self.cipherName),
            DifferentCipher(),
        )


class ExpandCipherStringTests(TestCase):
    """
    Tests for twisted.internet._sslverify._expandCipherString.
    """

    if skipSSL:
        skip = skipSSL

    def test_doesNotStumbleOverEmptyList(self):
        """
        If the expanded cipher list is empty, an empty L{list} is returned.
        """
        self.assertEqual(
            tuple(), sslverify._expandCipherString("", SSL.SSLv23_METHOD, 0)
        )

    def test_doesNotSwallowOtherSSLErrors(self):
        """
        Only no cipher matches get swallowed, every other SSL error gets
        propagated.
        """

        def raiser(_):
            # Unfortunately, there seems to be no way to trigger a real SSL
            # error artificially.
            raise SSL.Error([["", "", ""]])

        ctx = FakeContext(SSL.SSLv23_METHOD)
        ctx.set_cipher_list = raiser
        self.patch(sslverify.SSL, "Context", lambda _: ctx)
        self.assertRaises(
            SSL.Error, sslverify._expandCipherString, "ALL", SSL.SSLv23_METHOD, 0
        )

    def test_returnsTupleOfICiphers(self):
        """
        L{sslverify._expandCipherString} always returns a L{tuple} of
        L{interfaces.ICipher}.
        """
        ciphers = sslverify._expandCipherString("ALL", SSL.SSLv23_METHOD, 0)
        self.assertIsInstance(ciphers, tuple)
        bogus = []
        for c in ciphers:
            if not interfaces.ICipher.providedBy(c):
                bogus.append(c)

        self.assertEqual([], bogus)


class AcceptableCiphersTests(TestCase):
    """
    Tests for twisted.internet._sslverify.OpenSSLAcceptableCiphers.
    """

    if skipSSL:
        skip = skipSSL

    def test_selectOnEmptyListReturnsEmptyList(self):
        """
        If no ciphers are available, nothing can be selected.
        """
        ac = sslverify.OpenSSLAcceptableCiphers(tuple())
        self.assertEqual(tuple(), ac.selectCiphers(tuple()))

    def test_selectReturnsOnlyFromAvailable(self):
        """
        Select only returns a cross section of what is available and what is
        desirable.
        """
        ac = sslverify.OpenSSLAcceptableCiphers(
            [
                sslverify.OpenSSLCipher("A"),
                sslverify.OpenSSLCipher("B"),
            ]
        )
        self.assertEqual(
            (sslverify.OpenSSLCipher("B"),),
            ac.selectCiphers(
                [sslverify.OpenSSLCipher("B"), sslverify.OpenSSLCipher("C")]
            ),
        )

    def test_fromOpenSSLCipherStringExpandsToTupleOfCiphers(self):
        """
        If L{sslverify.OpenSSLAcceptableCiphers.fromOpenSSLCipherString} is
        called it expands the string to a tuple of ciphers.
        """
        ac = sslverify.OpenSSLAcceptableCiphers.fromOpenSSLCipherString("ALL")
        self.assertIsInstance(ac._ciphers, tuple)
        self.assertTrue(all(sslverify.ICipher.providedBy(c) for c in ac._ciphers))


class DiffieHellmanParametersTests(TestCase):
    """
    Tests for twisted.internet._sslverify.OpenSSLDHParameters.
    """

    if skipSSL:
        skip = skipSSL
    filePath = FilePath(b"dh.params")

    def test_fromFile(self):
        """
        Calling C{fromFile} with a filename returns an instance with that file
        name saved.
        """
        params = sslverify.OpenSSLDiffieHellmanParameters.fromFile(self.filePath)
        self.assertEqual(self.filePath, params._dhFile)


class FakeLibState:
    """
    State for L{FakeLib}

    @param setECDHAutoRaises: An exception
        L{FakeLib.SSL_CTX_set_ecdh_auto} should raise; if L{None},
        nothing is raised.

    @ivar ecdhContexts: A list of SSL contexts with which
        L{FakeLib.SSL_CTX_set_ecdh_auto} was called
    @type ecdhContexts: L{list} of L{OpenSSL.SSL.Context}s

    @ivar ecdhValues: A list of boolean values with which
        L{FakeLib.SSL_CTX_set_ecdh_auto} was called
    @type ecdhValues: L{list} of L{boolean}s
    """

    __slots__ = ("setECDHAutoRaises", "ecdhContexts", "ecdhValues")

    def __init__(self, setECDHAutoRaises):
        self.setECDHAutoRaises = setECDHAutoRaises
        self.ecdhContexts = []
        self.ecdhValues = []


class FakeLib:
    """
    An introspectable fake of cryptography's lib object.

    @param state: A L{FakeLibState} instance that contains this fake's
        state.
    """

    def __init__(self, state):
        self._state = state

    def SSL_CTX_set_ecdh_auto(self, ctx, value):
        """
        Record the context and value under in the C{_state} instance
        variable.

        @see: L{FakeLibState}

        @param ctx: An SSL context.
        @type ctx: L{OpenSSL.SSL.Context}

        @param value: A boolean value
        @type value: L{bool}
        """
        self._state.ecdhContexts.append(ctx)
        self._state.ecdhValues.append(value)
        if self._state.setECDHAutoRaises is not None:
            raise self._state.setECDHAutoRaises


class FakeLibTests(TestCase):
    """
    Tests for L{FakeLib}.
    """

    def test_SSL_CTX_set_ecdh_auto(self):
        """
        L{FakeLib.SSL_CTX_set_ecdh_auto} records context and value it
        was called with.
        """
        state = FakeLibState(setECDHAutoRaises=None)
        lib = FakeLib(state)
        self.assertNot(state.ecdhContexts)
        self.assertNot(state.ecdhValues)

        context, value = "CONTEXT", True
        lib.SSL_CTX_set_ecdh_auto(context, value)
        self.assertEqual(state.ecdhContexts, [context])
        self.assertEqual(state.ecdhValues, [True])

    def test_SSL_CTX_set_ecdh_autoRaises(self):
        """
        L{FakeLib.SSL_CTX_set_ecdh_auto} raises the exception provided
        by its state, while still recording its arguments.
        """
        state = FakeLibState(setECDHAutoRaises=ValueError)
        lib = FakeLib(state)
        self.assertNot(state.ecdhContexts)
        self.assertNot(state.ecdhValues)

        context, value = "CONTEXT", True
        self.assertRaises(ValueError, lib.SSL_CTX_set_ecdh_auto, context, value)
        self.assertEqual(state.ecdhContexts, [context])
        self.assertEqual(state.ecdhValues, [True])


class FakeCryptoState:
    """
    State for L{FakeCrypto}

    @param getEllipticCurveRaises: What
        L{FakeCrypto.get_elliptic_curve} should raise; L{None} and it
        won't raise anything

    @param getEllipticCurveReturns: What
        L{FakeCrypto.get_elliptic_curve} should return.

    @ivar getEllipticCurveCalls: The arguments with which
        L{FakeCrypto.get_elliptic_curve} has been called.
    @type getEllipticCurveCalls: L{list}
    """

    __slots__ = (
        "getEllipticCurveRaises",
        "getEllipticCurveReturns",
        "getEllipticCurveCalls",
    )

    def __init__(
        self,
        getEllipticCurveRaises,
        getEllipticCurveReturns,
    ):
        self.getEllipticCurveRaises = getEllipticCurveRaises
        self.getEllipticCurveReturns = getEllipticCurveReturns
        self.getEllipticCurveCalls = []


class FakeCrypto:
    """
    An introspectable fake of pyOpenSSL's L{OpenSSL.crypto} module.

    @ivar state: A L{FakeCryptoState} instance
    """

    def __init__(self, state):
        self._state = state

    def get_elliptic_curve(self, curve):
        """
        A fake that records the curve with which it was called.

        @param curve: see L{crypto.get_elliptic_curve}

        @return: see L{FakeCryptoState.getEllipticCurveReturns}
        @raises: see L{FakeCryptoState.getEllipticCurveRaises}
        """
        self._state.getEllipticCurveCalls.append(curve)
        if self._state.getEllipticCurveRaises is not None:
            raise self._state.getEllipticCurveRaises
        return self._state.getEllipticCurveReturns


class FakeCryptoTests(SynchronousTestCase):
    """
    Tests for L{FakeCrypto}.
    """

    def test_get_elliptic_curveRecordsArgument(self):
        """
        L{FakeCrypto.test_get_elliptic_curve} records the curve with
        which it was called.
        """
        state = FakeCryptoState(
            getEllipticCurveRaises=None,
            getEllipticCurveReturns=None,
        )
        crypto = FakeCrypto(state)
        crypto.get_elliptic_curve("a curve name")
        self.assertEqual(state.getEllipticCurveCalls, ["a curve name"])

    def test_get_elliptic_curveReturns(self):
        """
        L{FakeCrypto.test_get_elliptic_curve} returns the value
        specified by its state object and records what it was called
        with.
        """
        returnValue = "object"
        state = FakeCryptoState(
            getEllipticCurveRaises=None,
            getEllipticCurveReturns=returnValue,
        )
        crypto = FakeCrypto(state)
        self.assertIs(
            crypto.get_elliptic_curve("another curve name"),
            returnValue,
        )
        self.assertEqual(state.getEllipticCurveCalls, ["another curve name"])

    def test_get_elliptic_curveRaises(self):
        """
        L{FakeCrypto.test_get_elliptic_curve} raises the exception
        specified by its state object.
        """
        state = FakeCryptoState(
            getEllipticCurveRaises=ValueError, getEllipticCurveReturns=None
        )
        crypto = FakeCrypto(state)
        self.assertRaises(
            ValueError,
            crypto.get_elliptic_curve,
            "yet another curve name",
        )
        self.assertEqual(
            state.getEllipticCurveCalls,
            ["yet another curve name"],
        )


class ChooseDiffieHellmanEllipticCurveTests(SynchronousTestCase):
    """
    Tests for L{sslverify._ChooseDiffieHellmanEllipticCurve}.

    @cvar OPENSSL_110: A version number for OpenSSL 1.1.0

    @cvar OPENSSL_102: A version number for OpenSSL 1.0.2

    @cvar OPENSSL_101: A version number for OpenSSL 1.0.1

    @see:
        U{https://wiki.openssl.org/index.php/Manual:OPENSSL_VERSION_NUMBER(3)}
    """

    if skipSSL:
        skip = skipSSL

    OPENSSL_110 = 0x1010007F
    OPENSSL_102 = 0x100020EF
    OPENSSL_101 = 0x1000114F

    def setUp(self):
        self.libState = FakeLibState(setECDHAutoRaises=False)
        self.lib = FakeLib(self.libState)

        self.cryptoState = FakeCryptoState(
            getEllipticCurveReturns=None, getEllipticCurveRaises=None
        )
        self.crypto = FakeCrypto(self.cryptoState)
        self.context = FakeContext(SSL.SSLv23_METHOD)

    def test_openSSL110(self):
        """
        No configuration of contexts occurs under OpenSSL 1.1.0 and
        later, because they create contexts with secure ECDH curves.

        @see: U{http://twistedmatrix.com/trac/ticket/9210}
        """
        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_110,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(self.context)

        self.assertFalse(self.libState.ecdhContexts)
        self.assertFalse(self.libState.ecdhValues)
        self.assertFalse(self.cryptoState.getEllipticCurveCalls)
        self.assertIsNone(self.context._ecCurve)

    def test_openSSL102(self):
        """
        OpenSSL 1.0.2 does not set ECDH curves by default, but
        C{SSL_CTX_set_ecdh_auto} requests that a context choose a
        secure set curves automatically.
        """
        context = SSL.Context(SSL.SSLv23_METHOD)
        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_102,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(context)

        self.assertEqual(self.libState.ecdhContexts, [context._context])
        self.assertEqual(self.libState.ecdhValues, [True])
        self.assertFalse(self.cryptoState.getEllipticCurveCalls)
        self.assertIsNone(self.context._ecCurve)

    def test_openSSL102SetECDHAutoRaises(self):
        """
        An exception raised by C{SSL_CTX_set_ecdh_auto} under OpenSSL
        1.0.2 is suppressed because ECDH is best-effort.
        """
        self.libState.setECDHAutoRaises = BaseException
        context = SSL.Context(SSL.SSLv23_METHOD)
        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_102,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(context)

        self.assertEqual(self.libState.ecdhContexts, [context._context])
        self.assertEqual(self.libState.ecdhValues, [True])
        self.assertFalse(self.cryptoState.getEllipticCurveCalls)

    def test_openSSL101(self):
        """
        OpenSSL 1.0.1 does not set ECDH curves by default, nor does
        it expose L{SSL_CTX_set_ecdh_auto}.  Instead, a single ECDH
        curve can be set with L{OpenSSL.SSL.Context.set_tmp_ecdh}.
        """
        self.cryptoState.getEllipticCurveReturns = curve = "curve object"
        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_101,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(self.context)

        self.assertFalse(self.libState.ecdhContexts)
        self.assertFalse(self.libState.ecdhValues)
        self.assertEqual(
            self.cryptoState.getEllipticCurveCalls,
            [sslverify._defaultCurveName],
        )
        self.assertIs(self.context._ecCurve, curve)

    def test_openSSL101SetECDHRaises(self):
        """
        An exception raised by L{OpenSSL.SSL.Context.set_tmp_ecdh}
        under OpenSSL 1.0.1 is suppressed because ECHDE is best-effort.
        """

        def set_tmp_ecdh(ctx):
            raise BaseException

        self.context.set_tmp_ecdh = set_tmp_ecdh

        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_101,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(self.context)

        self.assertFalse(self.libState.ecdhContexts)
        self.assertFalse(self.libState.ecdhValues)
        self.assertEqual(
            self.cryptoState.getEllipticCurveCalls,
            [sslverify._defaultCurveName],
        )

    def test_openSSL101NoECC(self):
        """
        Contexts created under an OpenSSL 1.0.1 that doesn't support
        ECC have no configuration applied.
        """
        self.cryptoState.getEllipticCurveRaises = ValueError
        chooser = sslverify._ChooseDiffieHellmanEllipticCurve(
            self.OPENSSL_101,
            openSSLlib=self.lib,
            openSSLcrypto=self.crypto,
        )
        chooser.configureECDHCurve(self.context)

        self.assertFalse(self.libState.ecdhContexts)
        self.assertFalse(self.libState.ecdhValues)
        self.assertIsNone(self.context._ecCurve)


class KeyPairTests(TestCase):
    """
    Tests for L{sslverify.KeyPair}.
    """

    if skipSSL:
        skip = skipSSL

    def setUp(self):
        """
        Create test certificate.
        """
        self.sKey = makeCertificate(O=b"Server Test Certificate", CN=b"server")[0]

    def test_getstateDeprecation(self):
        """
        L{sslverify.KeyPair.__getstate__} is deprecated.
        """
        self.callDeprecated(
            (Version("Twisted", 15, 0, 0), "a real persistence system"),
            sslverify.KeyPair(self.sKey).__getstate__,
        )

    def test_setstateDeprecation(self):
        """
        {sslverify.KeyPair.__setstate__} is deprecated.
        """
        state = sslverify.KeyPair(self.sKey).dump()
        self.callDeprecated(
            (Version("Twisted", 15, 0, 0), "a real persistence system"),
            sslverify.KeyPair(self.sKey).__setstate__,
            state,
        )

    def test_noTrailingNewlinePemCert(self):
        noTrailingNewlineKeyPemPath = getModule("twisted.test").filePath.sibling(
            "cert.pem.no_trailing_newline"
        )

        certPEM = noTrailingNewlineKeyPemPath.getContent()
        ssl.Certificate.loadPEM(certPEM)
