# Source code for M2Crypto.SSL.Context

    """SSL Context

    Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""

    import os
    from M2Crypto import BIO, Err, RSA, X509, m2, types as C, util  # noqa
    from M2Crypto.SSL import cb  # noqa
    from M2Crypto.SSL.Session import Session  # noqa
    from weakref import WeakValueDictionary
    from typing import cast, Any, Callable, Optional, Union  # noqa

    __all__ = ["ctxmap", "Context", "map"]

    class _ctxmap(object):
        singleton: Optional["_ctxmap"] = None

        def __init__(self) -> None:
            """Simple WeakReffed list."""
            self._ctxmap: WeakValueDictionary[int, Any] = WeakValueDictionary()

        def __getitem__(self, key: int) -> Any:
            return self._ctxmap[key]

        def __setitem__(self, key: int, value: Any) -> None:
            self._ctxmap[key] = value

        def __delitem__(self, key: int) -> None:
            del self._ctxmap[key]

    def ctxmap() -> _ctxmap:
        if _ctxmap.singleton is None:
            _ctxmap.singleton = _ctxmap()
        return _ctxmap.singleton

    # deprecated!!!
    map = ctxmap

    class Context(object):
        """'Context' for SSL connections."""

        @staticmethod
        def m2_ssl_ctx_free(ctx: C.SSL_CTX) -> None:
            m2.ssl_ctx_free(ctx)

        def __init__(
            self,
            protocol: str = "tls",
            weak_crypto: Optional[int] = None,
            post_connection_check: Optional[Callable] = None,
        ) -> None:
            proto = getattr(m2, protocol + "_method", None)
            if proto is None:
                # default is 'sslv23' for older versions of OpenSSL
                if protocol == "tls":
                    proto = getattr(m2, "sslv23_method")
                else:
                    raise ValueError("no such protocol '%s'" % protocol)
            self.ctx = m2.ssl_ctx_new(proto())
            self.allow_unknown_ca: Union[int, bool] = 0
            self.post_connection_check = post_connection_check
            ctxmap()[id(self.ctx)] = self
            m2.ssl_ctx_set_cache_size(self.ctx, 128)
            if weak_crypto is None and protocol in ("sslv23", "tls"):
                self.set_options(m2.SSL_OP_ALL | m2.SSL_OP_NO_SSLv2 | m2.SSL_OP_NO_SSLv3)

        def __del__(self) -> None:
            if getattr(self, "ctx", None):
                self.m2_ssl_ctx_free(self.ctx)

        def close(self) -> None:
            del ctxmap()[id(self.ctx)]

        def load_cert(
            self,
            certfile: Union[str, bytes],
            keyfile: Union[str, bytes, None] = None,
            callback: Callable = util.passphrase_callback,
        ) -> None:
            """Load certificate and private key into the context.

            :param certfile: File that contains the PEM-encoded certificate.
            :param keyfile:  File that contains the PEM-encoded private key.
                             Default value of None indicates that the private key
                             is to be found in 'certfile'.
            :param callback: Callable object to be invoked if the private key is
                             passphrase-protected. Default callback provides a
                             simple terminal-style input for the passphrase.
            """
            m2.ssl_ctx_passphrase_callback(self.ctx, callback)
            certfile_str = (
                os.fsdecode(certfile) if isinstance(certfile, bytes) else certfile
            )
            m2.ssl_ctx_use_cert(self.ctx, certfile_str)
            if not keyfile:
                keyfile_str = certfile_str
            else:
                keyfile_str = (
                    os.fsdecode(keyfile) if isinstance(keyfile, bytes) else keyfile
                )
            m2.ssl_ctx_use_privkey(self.ctx, keyfile_str)
            if not m2.ssl_ctx_check_privkey(self.ctx):
                raise ValueError("public/private key mismatch")

        def load_cert_chain(
            self,
            certchainfile: Union[str, bytes],
            keyfile: Union[str, bytes, None] = None,
            callback: Callable = util.passphrase_callback,
        ) -> None:
            """Load certificate chain and private key into the context.

            :param certchainfile: File object containing the PEM-encoded
                                  certificate chain.
            :param keyfile:       File object containing the PEM-encoded private
                                  key. Default value of None indicates that the
                                  private key is to be found in 'certchainfile'.
            :param callback:      Callable object to be invoked if the private key
                                  is passphrase-protected. Default callback
                                  provides a simple terminal-style input for the
                                  passphrase.
            """
            m2.ssl_ctx_passphrase_callback(self.ctx, callback)
            certchainfile_str = (
                os.fsdecode(certchainfile)
                if isinstance(certchainfile, bytes)
                else certchainfile
            )
            m2.ssl_ctx_use_cert_chain(self.ctx, certchainfile_str)
            if not keyfile:
                keyfile_str = certchainfile_str
            else:
                keyfile_str = (
                    os.fsdecode(certchainfile)
                    if isinstance(certchainfile, bytes)
                    else certchainfile
                )
            m2.ssl_ctx_use_privkey(self.ctx, keyfile_str)
            if not m2.ssl_ctx_check_privkey(self.ctx):
                raise ValueError("public/private key mismatch")

        def set_client_CA_list_from_file(self, cafile: Union[str, bytes]) -> None:
            """Load CA certs into the context. These CA certs are sent to the
            peer during *SSLv3 certificate request*.

            :param cafile: File object containing one or more PEM-encoded CA
                           certificates concatenated together.
            """
            cafile_str = os.fsdecode(cafile) if isinstance(cafile, bytes) else cafile
            m2.ssl_ctx_set_client_CA_list_from_file(self.ctx, cafile_str)

        # Deprecated.
        load_client_CA = load_client_ca = set_client_CA_list_from_file

        def load_verify_locations(
            self,
            cafile: Union[str, bytes, None] = None,
            capath: Union[str, bytes, None] = None,
        ) -> int:
            """Load CA certs into the context.

            These CA certs are used during verification of the peer's
            certificate.

            :param cafile: File containing one or more PEM-encoded CA
                           certificates concatenated together.

            :param capath: Directory containing PEM-encoded CA certificates
                           (one certificate per file).

            :return: 0 if the operation failed because CAfile and CApath are NULL
                      or the processing at one of the locations specified failed.
                      Check the error stack to find out the reason.

                    1 The operation succeeded.
            """
            if cafile is None and capath is None:
                raise ValueError("cafile and capath can not both be None.")
            cafile_str = os.fsdecode(cafile) if isinstance(cafile, bytes) else cafile
            capath_str = os.fsdecode(capath) if isinstance(capath, bytes) else capath
            return m2.ssl_ctx_load_verify_locations(self.ctx, cafile_str, capath_str)

        # Deprecated.
        load_verify_info = load_verify_locations

        def set_session_id_ctx(self, id: bytes) -> None:
            """Sets the session id for the SSL.Context w/in a session can be reused.

            :param id: Sessions are generated within a certain context. When
                       exporting/importing sessions with
                       i2d_SSL_SESSION/d2i_SSL_SESSION it would be possible,
                       to re-import a session generated from another context
                       (e.g. another application), which might lead to
                       malfunctions. Therefore each application must set its
                       own session id context sid_ctx which is used to
                       distinguish the contexts and is stored in exported
                       sessions. The sid_ctx can be any kind of binary data
                       with a given length, it is therefore possible to use
                       e.g. the name of the application and/or the hostname
                       and/or service name.
            """
            ret = m2.ssl_ctx_set_session_id_context(self.ctx, id)
            if not ret:
                raise Err.SSLError(Err.get_error_code(), "")

        def set_default_verify_paths(self) -> int:
            """
            Specifies that the default locations from which CA certs are
            loaded should be used.

            There is one default directory and one default file. The default
            CA certificates directory is called "certs" in the default
            OpenSSL directory. Alternatively the SSL_CERT_DIR environment
            variable can be defined to override this location. The default
            CA certificates file is called "cert.pem" in the default OpenSSL
            directory. Alternatively the SSL_CERT_FILE environment variable
            can be defined to override this location.

            @return 0 if the operation failed. A missing default location is
                      still treated as a success. No error code is set.

                    1 The operation succeeded.
            """
            ret = m2.ssl_ctx_set_default_verify_paths(self.ctx)
            if not ret:
                raise ValueError("Cannot use default SSL certificate store!")
            return ret

        def set_allow_unknown_ca(self, ok: Union[int, bool]) -> None:
            """Set the context to accept/reject a peer certificate if the
            certificate's CA is unknown.

            :param ok:       True to accept, False to reject.
            """
            self.allow_unknown_ca = ok

        def get_allow_unknown_ca(self) -> Union[int, bool]:
            """Get the context's setting that accepts/rejects a peer
            certificate if the certificate's CA is unknown.

            FIXME 2Bconverted to bool
            """
            return self.allow_unknown_ca

        def set_verify(
            self,
            mode: int,
            depth: int,
            callback: Optional[Callable[[int, C.X509_STORE_CTX], int]] = None,
        ) -> None:
            """
            Set verify options. Most applications will need to call this
            method with the right options to make a secure SSL connection.

            :param mode:     The verification mode to use. Typically at least
                             SSL.verify_peer is used. Clients would also typically
                             add SSL.verify_fail_if_no_peer_cert.
            :param depth:    The maximum allowed depth of the certificate chain
                             returned by the peer.
            :param callback: Callable that can be used to specify custom
                             verification checks.
            """
            if callback is None:
                m2.ssl_ctx_set_verify_default(self.ctx, mode)
            else:
                m2.ssl_ctx_set_verify(self.ctx, mode, callback)
            m2.ssl_ctx_set_verify_depth(self.ctx, depth)

        def get_verify_mode(self) -> int:
            return m2.ssl_ctx_get_verify_mode(self.ctx)

        def get_verify_depth(self) -> int:
            """Returns the verification mode currently set in the SSL Context."""
            return m2.ssl_ctx_get_verify_depth(self.ctx)

        def set_tmp_dh(self, dhpfile: Union[str, bytes]) -> int:
            """Load ephemeral DH parameters into the context.

            :param dhpfile: Filename of the file containing the PEM-encoded
                            DH parameters.
            """
            f = BIO.openfile(dhpfile)
            dhp = m2.dh_read_parameters(f.bio_ptr())
            if dhp is None:
                raise BIO.BIOError("Could not read DH parameters")
            return m2.ssl_ctx_set_tmp_dh(self.ctx, dhp)

        def set_tmp_dh_callback(self, callback: Optional[Callable] = None) -> None:
            """Sets the callback function for SSL.Context.

            :param callback: Callable to be used when a DH parameters are required.
            """
            if callback is not None:
                m2.ssl_ctx_set_tmp_dh_callback(self.ctx, callback)

        def set_tmp_rsa(self, rsa: RSA.RSA) -> int:
            """Load ephemeral RSA key into the context.

            :param rsa: RSA.RSA instance.
            """
            if isinstance(rsa, RSA.RSA):
                return m2.ssl_ctx_set_tmp_rsa(self.ctx, rsa.rsa)
            else:
                raise TypeError("Expected an instance of RSA.RSA, got %s." % rsa)

        def set_tmp_rsa_callback(self, callback: Optional[Callable] = None) -> None:
            """Sets the callback function to be used when
            a temporary/ephemeral RSA key is required.
            """
            if callback is not None:
                m2.ssl_ctx_set_tmp_rsa_callback(self.ctx, callback)

        def set_info_callback(self, callback: Callable = cb.ssl_info_callback) -> None:
            """Set a callback function to get state information.

            It can be used to get state information about the SSL
            connections that are created from this context.

            :param callback: Callback function. The default prints
                             information to stderr.
            """
            m2.ssl_ctx_set_info_callback(self.ctx, callback)

        def set_cipher_list(self, cipher_list: str) -> int:
            """Sets the list of available ciphers.

            :param cipher_list: The format of the string is described in
                                ciphers(1).
            :return: 1 if any cipher could be selected and 0 on complete
                     failure.
            """
            return m2.ssl_ctx_set_cipher_list(self.ctx, cipher_list)

        def add_session(self, session: Session) -> int:
            """Add the session to the context.

            :param session: the session to be added.

            :return: 0 The operation failed. It was tried to add the same
                       (identical) session twice.

                     1 The operation succeeded.
            """
            return m2.ssl_ctx_add_session(self.ctx, cast(C.SSL_SESSION, session._ptr()))

        def remove_session(self, session: Session) -> int:
            """Remove the session from the context.

            :param session: the session to be removed.

            :return: 0 The operation failed. The session was not found in
                       the cache.

                     1 The operation succeeded.
            """
            return m2.ssl_ctx_remove_session(self.ctx, cast(C.SSL_SESSION, session._ptr()))

        def get_session_timeout(self) -> int:
            """Get current session timeout.

            Whenever a new session is created, it is assigned a maximum
            lifetime.  This lifetime is specified by storing the creation
            time of the session and the timeout value valid at this time. If
            the actual time is later than creation time plus timeout, the
            session is not reused.

            Due to this realization, all sessions behave according to the
            timeout value valid at the time of the session negotiation.
            Changes of the timeout value do not affect already established
            sessions.

            Expired sessions are removed from the internal session cache,
            whenever SSL_CTX_flush_sessions(3) is called, either directly by
            the application or automatically (see
            SSL_CTX_set_session_cache_mode(3))

            The default value for session timeout is decided on a per
            protocol basis, see SSL_get_default_timeout(3).  All currently
            supported protocols have the same default timeout value of 300
            seconds.

            SSL_CTX_set_timeout() returns the previously set timeout value.

            :return: the currently set timeout value.
            """
            return m2.ssl_ctx_get_session_timeout(self.ctx)

        def set_session_timeout(self, timeout: int) -> int:
            """Set new session timeout.

            See self.get_session_timeout() for explanation of the session
            timeouts.

            :param timeout: new timeout value.

            :return: the previously set timeout value.
            """
            return m2.ssl_ctx_set_session_timeout(self.ctx, timeout)

        def set_session_cache_mode(self, mode: int) -> int:
            """Enables/disables session caching.

            The mode is set by using m2.SSL_SESS_CACHE_* constants.

            :param mode: new mode value.

            :return: the previously set cache mode value.
            """
            return m2.ssl_ctx_set_session_cache_mode(self.ctx, mode)

        def get_session_cache_mode(self) -> int:
            """Gets the current session caching.

            The mode is set to m2.SSL_SESS_CACHE_* constants.

            :return: the previously set cache mode value.
            """
            return m2.ssl_ctx_get_session_cache_mode(self.ctx)

        def set_options(self, op: int) -> int:
            """Adds the options set via bitmask in options to the Context.

            !!! Options already set before are not cleared!

            The behaviour of the SSL library can be changed by setting
            several options.  The options are coded as bitmasks and can be
            combined by a logical or operation (|).

            SSL.Context.set_options() and SSL.set_options() affect the
            (external) protocol behaviour of the SSL library. The (internal)
            behaviour of the API can be changed by using the similar
            SSL.Context.set_mode() and SSL.set_mode() functions.

            During a handshake, the option settings of the SSL object are
            used. When a new SSL object is created from a context using
            SSL(), the current option setting is copied. Changes to ctx
            do not affect already created SSL objects. SSL.clear() does not
            affect the settings.

            :param op: bitmask of additional options specified in
                       SSL_CTX_set_options(3) manpage.

            :return: the new options bitmask after adding options.
            """
            return m2.ssl_ctx_set_options(self.ctx, op)

        def get_cert_store(self) -> X509.X509_Store:
            """
            Get the certificate store associated with this context.

            :warning: The store is NOT refcounted, and as such can not be relied
                      to be valid once the context goes away or is changed.
            """
            return X509.X509_Store(m2.ssl_ctx_get_cert_store(self.ctx))