예제 #1
0
    def read(self, length):
        """
            If length is -1, we read until connection closes.
        """
        result = b''
        start = time.time()
        while length == -1 or length > 0:
            if length == -1 or length > self.BLOCKSIZE:
                rlen = self.BLOCKSIZE
            else:
                rlen = length
            try:
                data = self.o.read(rlen)
            except SSL.ZeroReturnError:
                # TLS connection was shut down cleanly
                break
            except (SSL.WantWriteError, SSL.WantReadError):
                # From the OpenSSL docs:
                # If the underlying BIO is non-blocking, SSL_read() will also return when the
                # underlying BIO could not satisfy the needs of SSL_read() to continue the
                # operation. In this case a call to SSL_get_error with the return value of
                # SSL_read() will yield SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
                # 300 is OpenSSL default timeout
                timeout = self.o.gettimeout() or 300
                if (time.time() - start) < timeout:
                    time.sleep(0.1)
                    continue
                else:
                    raise exceptions.TcpTimeout()
            except socket.timeout:
                raise exceptions.TcpTimeout()
            except OSError as e:
                raise exceptions.TcpDisconnect(str(e))
            except SSL.SysCallError as e:
                if e.args == (-1, 'Unexpected EOF'):
                    break
                raise exceptions.TlsException(str(e))
            except SSL.Error as e:
                raise exceptions.TlsException(str(e))
            self.first_byte_timestamp = self.first_byte_timestamp or time.time(
            )

            if not data:
                break
            result += data
            if length != -1:
                length -= len(data)
        self.add_log(result)
        return result
예제 #2
0
    def convert_to_tls(self, sni=None, alpn_protos=None, **sslctx_kwargs):
        context = tls.create_client_context(
            alpn_protos=alpn_protos,
            sni=sni,
            **sslctx_kwargs
        )
        self.connection = SSL.Connection(context, self.connection)
        if sni:
            self.sni = sni
            self.connection.set_tlsext_host_name(sni.encode("idna"))
        self.connection.set_connect_state()
        try:
            self.connection.do_handshake()
        except SSL.Error as v:
            if self.ssl_verification_error:
                raise self.ssl_verification_error
            else:
                raise exceptions.TlsException("SSL handshake error: %s" % repr(v))

        self.cert = certs.Cert(self.connection.get_peer_certificate())

        # Keep all server certificates in a list
        for i in self.connection.get_peer_cert_chain():
            self.server_certs.append(certs.Cert(i))

        self.tls_established = True
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
예제 #3
0
 def create_ssl_context(self, cert=None, alpn_protos=None, **sslctx_kwargs):
     context = self._create_ssl_context(alpn_protos=alpn_protos,
                                        **sslctx_kwargs)
     # Client Certs
     if cert:
         try:
             context.use_privatekey_file(cert)
             context.use_certificate_file(cert)
         except SSL.Error as v:
             raise exceptions.TlsException(
                 "SSL client certificate error: %s" % str(v))
     return context
예제 #4
0
    def convert_to_ssl(self, cert, key, **sslctx_kwargs):
        """
        Convert connection to SSL.
        For a list of parameters, see BaseHandler._create_ssl_context(...)
        """

        context = self.create_ssl_context(cert, key, **sslctx_kwargs)
        self.connection = SSL.Connection(context, self.connection)
        self.connection.set_accept_state()
        try:
            self.connection.do_handshake()
        except SSL.Error as v:
            raise exceptions.TlsException("SSL handshake error: %s" % repr(v))
        self.ssl_established = True
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
예제 #5
0
    def convert_to_ssl(self, cert, key, **sslctx_kwargs):
        """
        Convert connection to SSL.
        For a list of parameters, see tls.create_server_context(...)
        """

        context = tls.create_server_context(cert=cert,
                                            key=key,
                                            **sslctx_kwargs)
        self.connection = SSL.Connection(context, self.connection)
        self.connection.set_accept_state()
        try:
            self.connection.do_handshake()
        except SSL.Error as v:
            raise exceptions.TlsException("SSL handshake error: %s" % repr(v))
        self.ssl_established = True
        cert = self.connection.get_peer_certificate()
        if cert:
            self.clientcert = certs.SSLCert(cert)
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
예제 #6
0
    def convert_to_tls(self, sni=None, alpn_protos=None, **sslctx_kwargs):
        context = tls.create_client_context(alpn_protos=alpn_protos,
                                            sni=sni,
                                            **sslctx_kwargs)
        self.connection = SSL.Connection(context, self.connection)
        if sni:
            self.sni = sni
            self.connection.set_tlsext_host_name(sni.encode("idna"))
        self.connection.set_connect_state()
        try:
            idle_timeout = self.connection_idle_seconds
            if idle_timeout != -1:
                timeout = idle_timeout
            else:
                timeout = 60
            if self.channel is not None:
                self.channel.ask("ssl_handshake_started", self)
            TimeoutHelper.wrap_with_timeout(self.connection.do_handshake,
                                            timeout)
        except SSL.Error as v:
            if self.ssl_verification_error:
                raise self.ssl_verification_error
            else:
                raise exceptions.TlsException("SSL handshake error: %s" %
                                              repr(v))
        finally:
            if self.channel is not None:
                self.channel.ask("ssl_handshake_finished", self)

        self.cert = certs.Cert(self.connection.get_peer_certificate())

        # Keep all server certificates in a list
        for i in self.connection.get_peer_cert_chain():
            self.server_certs.append(certs.Cert(i))

        self.tls_established = True
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
예제 #7
0
    def peek(self, length):
        """
        Tries to peek into the underlying file object.

        Returns:
            Up to the next N bytes if peeking is successful.

        Raises:
            exceptions.TcpException if there was an error with the socket
            TlsException if there was an error with pyOpenSSL.
            NotImplementedError if the underlying file object is not a [pyOpenSSL] socket
        """
        if isinstance(self.o, socket_fileobject):
            try:
                return self.o._sock.recv(length, socket.MSG_PEEK)
            except socket.error as e:
                raise exceptions.TcpException(repr(e))
        elif isinstance(self.o, SSL.Connection):
            try:
                return self.o.recv(length, socket.MSG_PEEK)
            except SSL.Error as e:
                raise exceptions.TlsException(str(e))
        else:
            raise NotImplementedError("Can only peek into (pyOpenSSL) sockets")
예제 #8
0
    def convert_to_ssl(self, sni=None, alpn_protos=None, **sslctx_kwargs):
        """
            cert: Path to a file containing both client cert and private key.

            options: A bit field consisting of OpenSSL.SSL.OP_* values
            verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values
            ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool
            ca_pemfile: Path to a PEM formatted trusted CA certificate
        """
        verification_mode = sslctx_kwargs.get('verify_options', None)
        if verification_mode == SSL.VERIFY_PEER and not sni:
            raise exceptions.TlsException(
                "Cannot validate certificate hostname without SNI")

        context = self.create_ssl_context(alpn_protos=alpn_protos,
                                          sni=sni,
                                          **sslctx_kwargs)
        self.connection = SSL.Connection(context, self.connection)
        if sni:
            self.sni = sni
            self.connection.set_tlsext_host_name(sni.encode("idna"))
        self.connection.set_connect_state()
        try:
            self.connection.do_handshake()
        except SSL.Error as v:
            if self.ssl_verification_error:
                raise self.ssl_verification_error
            else:
                raise exceptions.TlsException("SSL handshake error: %s" %
                                              repr(v))

        self.cert = certs.SSLCert(self.connection.get_peer_certificate())

        # Keep all server certificates in a list
        for i in self.connection.get_peer_cert_chain():
            self.server_certs.append(certs.SSLCert(i))

        # Validate TLS Hostname
        try:
            crt = dict(subjectAltName=[("DNS", x.decode("ascii", "strict"))
                                       for x in self.cert.altnames])
            if self.cert.cn:
                crt["subject"] = [[[
                    "commonName",
                    self.cert.cn.decode("ascii", "strict")
                ]]]
            if sni:
                # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding
                # Conversion algorithm is in RFC 3490 which is implemented by idna codec
                # https://docs.python.org/3/library/codecs.html#text-encodings
                # https://tools.ietf.org/html/rfc6066#section-3
                # https://tools.ietf.org/html/rfc4985#section-3
                hostname = sni.encode("idna").decode("ascii")
            else:
                hostname = "no-hostname"
            match_hostname(crt, hostname)
        except (ValueError, CertificateError) as e:
            self.ssl_verification_error = exceptions.InvalidCertificateException(
                "Certificate Verification Error for {}: {}".format(
                    sni or repr(self.address), str(e)))
            if verification_mode == SSL.VERIFY_PEER:
                raise self.ssl_verification_error

        self.ssl_established = True
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
예제 #9
0
    def _create_ssl_context(
        self,
        method=SSL_DEFAULT_METHOD,
        options=SSL_DEFAULT_OPTIONS,
        verify_options=SSL.VERIFY_NONE,
        ca_path=None,
        ca_pemfile=None,
        cipher_list=None,
        alpn_protos=None,
        alpn_select=None,
        alpn_select_callback=None,
        sni=None,
    ):
        """
        Creates an SSL Context.

        :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD
        :param options: A bit field consisting of OpenSSL.SSL.OP_* values
        :param verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values
        :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool
        :param ca_pemfile: Path to a PEM formatted trusted CA certificate
        :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html
        :rtype : SSL.Context
        """
        try:
            context = SSL.Context(method)
        except ValueError as e:
            method_name = ssl_method_names.get(method, "unknown")
            raise exceptions.TlsException(
                "SSL method \"%s\" is most likely not supported "
                "or disabled (for security reasons) in your libssl. "
                "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 "
                "for more details." % method_name)

        # Options (NO_SSLv2/3)
        if options is not None:
            context.set_options(options)

        # Verify Options (NONE/PEER and trusted CAs)
        if verify_options is not None:

            def verify_cert(conn, x509, errno, err_depth, is_cert_verified):
                if not is_cert_verified:
                    self.ssl_verification_error = exceptions.InvalidCertificateException(
                        "Certificate Verification Error for {}: {} (errno: {}, depth: {})"
                        .format(
                            sni,
                            strutils.always_str(
                                SSL._ffi.string(
                                    SSL._lib.X509_verify_cert_error_string(
                                        errno)), "utf8"), errno, err_depth))
                return is_cert_verified

            context.set_verify(verify_options, verify_cert)
            if ca_path is None and ca_pemfile is None:
                ca_pemfile = certifi.where()
            try:
                context.load_verify_locations(ca_pemfile, ca_path)
            except SSL.Error:
                raise exceptions.TlsException(
                    "Cannot load trusted certificates ({}, {}).".format(
                        ca_pemfile, ca_path))

        # Workaround for
        # https://github.com/pyca/pyopenssl/issues/190
        # https://github.com/mitmproxy/mitmproxy/issues/472
        # Options already set before are not cleared.
        context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY)

        # Cipher List
        if cipher_list:
            try:
                context.set_cipher_list(cipher_list.encode())
            except SSL.Error as v:
                raise exceptions.TlsException(
                    "SSL cipher specification error: %s" % str(v))

        # SSLKEYLOGFILE
        if log_ssl_key:
            context.set_info_callback(log_ssl_key)

        if alpn_protos is not None:
            # advertise application layer protocols
            context.set_alpn_protos(alpn_protos)
        elif alpn_select is not None and alpn_select_callback is None:
            # select application layer protocol
            def alpn_select_callback(conn_, options):
                if alpn_select in options:
                    return bytes(alpn_select)
                else:  # pragma: no cover
                    return options[0]

            context.set_alpn_select_callback(alpn_select_callback)
        elif alpn_select_callback is not None and alpn_select is None:
            if not callable(alpn_select_callback):
                raise exceptions.TlsException(
                    "ALPN error: alpn_select_callback must be a function.")
            context.set_alpn_select_callback(alpn_select_callback)
        elif alpn_select_callback is not None and alpn_select is not None:
            raise exceptions.TlsException(
                "ALPN error: only define alpn_select (string) OR alpn_select_callback (function)."
            )

        return context
예제 #10
0
    def convert_to_ssl(self, sni=None, alpn_protos=None, **sslctx_kwargs):
        """
            cert: Path to a file containing both client cert and private key.

            options: A bit field consisting of OpenSSL.SSL.OP_* values
            verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values
            ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool
            ca_pemfile: Path to a PEM formatted trusted CA certificate
        """
        verification_mode = sslctx_kwargs.get('verify_options', None)
        if verification_mode == SSL.VERIFY_PEER and not sni:
            raise exceptions.TlsException(
                "Cannot validate certificate hostname without SNI")

        context = self.create_ssl_context(alpn_protos=alpn_protos,
                                          sni=sni,
                                          **sslctx_kwargs)
        self.connection = SSL.Connection(context, self.connection)
        if sni:
            self.sni = sni
            self.connection.set_tlsext_host_name(sni.encode("idna"))
        self.connection.set_connect_state()
        try:
            self.connection.do_handshake()
        except SSL.Error as v:
            if self.ssl_verification_error:
                raise self.ssl_verification_error
            else:
                raise exceptions.TlsException("SSL handshake error: %s" %
                                              repr(v))
        else:
            # Fix for pre v1.0 OpenSSL, which doesn't throw an exception on
            # certificate validation failure
            if verification_mode == SSL.VERIFY_PEER and self.ssl_verification_error:
                raise self.ssl_verification_error

        self.cert = certs.SSLCert(self.connection.get_peer_certificate())

        # Keep all server certificates in a list
        for i in self.connection.get_peer_cert_chain():
            self.server_certs.append(certs.SSLCert(i))

        # Validate TLS Hostname
        try:
            crt = dict(subjectAltName=[("DNS", x.decode("ascii", "strict"))
                                       for x in self.cert.altnames])
            if self.cert.cn:
                crt["subject"] = [[[
                    "commonName",
                    self.cert.cn.decode("ascii", "strict")
                ]]]
            if sni:
                hostname = sni
            else:
                hostname = "no-hostname"
            match_hostname(crt, hostname)
        except (ValueError, CertificateError) as e:
            self.ssl_verification_error = exceptions.InvalidCertificateException(
                "Certificate Verification Error for {}: {}".format(
                    sni or repr(self.address), str(e)))
            if verification_mode == SSL.VERIFY_PEER:
                raise self.ssl_verification_error

        self.ssl_established = True
        self.rfile.set_descriptor(self.connection)
        self.wfile.set_descriptor(self.connection)
def create_client_context(
        cert: str = None,
        sni: str = None,
        address: str = None,
        verify: int = SSL.VERIFY_NONE,
        **sslctx_kwargs
) -> SSL.Context:
    """
    Args:
        cert: Path to a file containing both client cert and private key.
        sni: Server Name Indication. Required for VERIFY_PEER
        address: server address, used for expressive error messages only
        verify: A bit field consisting of OpenSSL.SSL.VERIFY_* values
    """

    if sni is None and verify != SSL.VERIFY_NONE:
        raise exceptions.TlsException("Cannot validate certificate hostname without SNI")

    def verify_callback(
            conn: SSL.Connection,
            x509: SSL.X509,
            errno: int,
            depth: int,
            is_cert_verified: bool
    ) -> bool:
        if is_cert_verified and depth == 0 and not sni:
            conn.cert_error = exceptions.InvalidCertificateException(
                f"Certificate verification error for {address}: Cannot validate hostname, SNI missing."
            )
            is_cert_verified = False
        elif is_cert_verified:
            pass
        else:
            conn.cert_error = exceptions.InvalidCertificateException(
                "Certificate verification error for {}: {} (errno: {}, depth: {})".format(
                    sni,
                    SSL._ffi.string(SSL._lib.X509_verify_cert_error_string(errno)).decode(),
                    errno,
                    depth
                )
            )

        # SSL_VERIFY_NONE: The handshake will be continued regardless of the verification result.
        return is_cert_verified

    context = _create_ssl_context(
        verify=verify,
        verify_callback=verify_callback,
        **sslctx_kwargs,
    )

    if sni:
        # Manually enable hostname verification on the context object.
        # https://wiki.openssl.org/index.php/Hostname_validation
        param = SSL._lib.SSL_CTX_get0_param(context._context)
        # Matching on the CN is disabled in both Chrome and Firefox, so we disable it, too.
        # https://www.chromestatus.com/feature/4981025180483584
        SSL._lib.X509_VERIFY_PARAM_set_hostflags(
            param,
            SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | SSL._lib.X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
        )
        SSL._openssl_assert(
            SSL._lib.X509_VERIFY_PARAM_set1_host(param, sni.encode("idna"), 0) == 1
        )

    # Client Certs
    if cert:
        try:
            context.use_privatekey_file(cert)
            context.use_certificate_chain_file(cert)
        except SSL.Error as v:
            raise exceptions.TlsException("SSL client certificate error: %s" % str(v))
    return context
def _create_ssl_context(
        method: int = DEFAULT_METHOD,
        options: int = DEFAULT_OPTIONS,
        ca_path: str = None,
        ca_pemfile: str = None,
        cipher_list: str = None,
        alpn_protos: typing.Iterable[bytes] = None,
        alpn_select=None,
        alpn_select_callback: typing.Callable[[typing.Any, typing.Any], bytes] = None,
        verify: int = SSL.VERIFY_PEER,
        verify_callback: typing.Optional[
            typing.Callable[[SSL.Connection, SSL.X509, int, int, bool], bool]
        ] = None,
) -> SSL.Context:
    """
    Creates an SSL Context.

    :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD
    :param options: A bit field consisting of OpenSSL.SSL.OP_* values
    :param verify: A bit field consisting of OpenSSL.SSL.VERIFY_* values
    :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool
    :param ca_pemfile: Path to a PEM formatted trusted CA certificate
    :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html
    :rtype : SSL.Context
    """
    try:
        context = SSL.Context(method)
    except ValueError:
        method_name = METHOD_NAMES.get(method, "unknown")
        raise exceptions.TlsException(
            "SSL method \"%s\" is most likely not supported "
            "or disabled (for security reasons) in your libssl. "
            "Please refer to https://github.com/mitmproxy/mitmproxy/issues/1101 "
            "for more details." % method_name
        )

    # Options (NO_SSLv2/3)
    if options is not None:
        context.set_options(options)

    # Verify Options (NONE/PEER and trusted CAs)
    if verify is not None:
        context.set_verify(verify, verify_callback)
        if ca_path is None and ca_pemfile is None:
            ca_pemfile = certifi.where()
        try:
            context.load_verify_locations(ca_pemfile, ca_path)
        except SSL.Error:
            raise exceptions.TlsException(
                "Cannot load trusted certificates ({}, {}).".format(
                    ca_pemfile, ca_path
                )
            )

    # Workaround for
    # https://github.com/pyca/pyopenssl/issues/190
    # https://github.com/mitmproxy/mitmproxy/issues/472
    # Options already set before are not cleared.
    context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY)

    # Cipher List
    if cipher_list:
        try:
            context.set_cipher_list(cipher_list.encode())
        except SSL.Error as v:
            raise exceptions.TlsException("SSL cipher specification error: %s" % str(v))

    # SSLKEYLOGFILE
    if log_master_secret:
        context.set_keylog_callback(log_master_secret)

    if alpn_protos is not None:
        # advertise application layer protocols
        context.set_alpn_protos(alpn_protos)
    elif alpn_select is not None and alpn_select_callback is None:
        # select application layer protocol
        def alpn_select_callback(conn_, options):
            if alpn_select in options:
                return bytes(alpn_select)
            else:  # pragma: no cover
                return options[0]

        context.set_alpn_select_callback(alpn_select_callback)
    elif alpn_select_callback is not None and alpn_select is None:
        if not callable(alpn_select_callback):
            raise exceptions.TlsException("ALPN error: alpn_select_callback must be a function.")
        context.set_alpn_select_callback(alpn_select_callback)
    elif alpn_select_callback is not None and alpn_select is not None:
        raise exceptions.TlsException(
            "ALPN error: only define alpn_select (string) OR alpn_select_callback (function).")

    return context
예제 #13
0
def create_client_context(cert: str = None,
                          sni: str = None,
                          address: str = None,
                          verify: int = SSL.VERIFY_NONE,
                          **sslctx_kwargs) -> SSL.Context:
    """
    Args:
        cert: Path to a file containing both client cert and private key.
        sni: Server Name Indication. Required for VERIFY_PEER
        address: server address, used for expressive error messages only
        verify: A bit field consisting of OpenSSL.SSL.VERIFY_* values
    """

    if sni is None and verify != SSL.VERIFY_NONE:
        raise exceptions.TlsException(
            "Cannot validate certificate hostname without SNI")

    def verify_callback(conn: SSL.Connection, x509: SSL.X509, errno: int,
                        depth: int, is_cert_verified: bool) -> bool:
        if is_cert_verified and depth == 0:
            # Verify hostname of leaf certificate.
            cert = certs.Cert(x509)
            try:
                crt = dict(subjectAltName=[
                    ("DNS", x.decode("ascii", "strict")) for x in cert.altnames
                ])  # type: typing.Dict[str, typing.Any]
                if cert.cn:
                    crt["subject"] = [[[
                        "commonName",
                        cert.cn.decode("ascii", "strict")
                    ]]]
                if sni:
                    # SNI hostnames allow support of IDN by using ASCII-Compatible Encoding
                    # Conversion algorithm is in RFC 3490 which is implemented by idna codec
                    # https://docs.python.org/3/library/codecs.html#text-encodings
                    # https://tools.ietf.org/html/rfc6066#section-3
                    # https://tools.ietf.org/html/rfc4985#section-3
                    hostname = sni.encode("idna").decode("ascii")
                else:
                    hostname = "no-hostname"
                match_hostname(crt, hostname)
            except (ValueError, CertificateError) as e:
                conn.cert_error = exceptions.InvalidCertificateException(
                    "Certificate verification error for {}: {}".format(
                        sni or repr(address), str(e)))
                is_cert_verified = False
        elif is_cert_verified:
            pass
        else:
            conn.cert_error = exceptions.InvalidCertificateException(
                "Certificate verification error for {}: {} (errno: {}, depth: {})"
                .format(
                    sni,
                    SSL._ffi.string(
                        SSL._lib.X509_verify_cert_error_string(
                            errno)).decode(), errno, depth))

        # SSL_VERIFY_NONE: The handshake will be continued regardless of the verification result.
        return is_cert_verified

    context = _create_ssl_context(
        verify=verify,
        verify_callback=verify_callback,
        **sslctx_kwargs,
    )

    # Client Certs
    if cert:
        try:
            context.use_privatekey_file(cert)
            context.use_certificate_file(cert)
        except SSL.Error as v:
            raise exceptions.TlsException("SSL client certificate error: %s" %
                                          str(v))
    return context
예제 #14
0
    def _create_ssl_context(self,
                            method=SSL_DEFAULT_METHOD,
                            options=SSL_DEFAULT_OPTIONS,
                            verify_options=SSL.VERIFY_NONE,
                            ca_path=None,
                            ca_pemfile=None,
                            cipher_list=None,
                            alpn_protos=None,
                            alpn_select=None,
                            alpn_select_callback=None,
                            sni=None,
                            ):
        """
        Creates an SSL Context.

        :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, or TLSv1_2_METHOD
        :param options: A bit field consisting of OpenSSL.SSL.OP_* values
        :param verify_options: A bit field consisting of OpenSSL.SSL.VERIFY_* values
        :param ca_path: Path to a directory of trusted CA certificates prepared using the c_rehash tool
        :param ca_pemfile: Path to a PEM formatted trusted CA certificate
        :param cipher_list: A textual OpenSSL cipher list, see https://www.openssl.org/docs/apps/ciphers.html
        :rtype : SSL.Context
        """
        context = SSL.Context(method)
        # Options (NO_SSLv2/3)
        if options is not None:
            context.set_options(options)

        # Verify Options (NONE/PEER and trusted CAs)
        if verify_options is not None:
            def verify_cert(conn, x509, errno, err_depth, is_cert_verified):
                if not is_cert_verified:
                    self.ssl_verification_error = exceptions.InvalidCertificateException(
                        "Certificate Verification Error for {}: {} (errno: {}, depth: {})".format(
                            sni,
                            strutils.native(SSL._ffi.string(SSL._lib.X509_verify_cert_error_string(errno)), "utf8"),
                            errno,
                            err_depth
                        )
                    )
                return is_cert_verified

            context.set_verify(verify_options, verify_cert)
            if ca_path is None and ca_pemfile is None:
                ca_pemfile = certifi.where()
            context.load_verify_locations(ca_pemfile, ca_path)

        # Workaround for
        # https://github.com/pyca/pyopenssl/issues/190
        # https://github.com/mitmproxy/mitmproxy/issues/472
        # Options already set before are not cleared.
        context.set_mode(SSL._lib.SSL_MODE_AUTO_RETRY)

        # Cipher List
        if cipher_list:
            try:
                context.set_cipher_list(cipher_list)

                # TODO: maybe change this to with newer pyOpenSSL APIs
                context.set_tmp_ecdh(OpenSSL.crypto.get_elliptic_curve('prime256v1'))
            except SSL.Error as v:
                raise exceptions.TlsException("SSL cipher specification error: %s" % str(v))

        # SSLKEYLOGFILE
        if log_ssl_key:
            context.set_info_callback(log_ssl_key)

        if HAS_ALPN:
            if alpn_protos is not None:
                # advertise application layer protocols
                context.set_alpn_protos(alpn_protos)
            elif alpn_select is not None and alpn_select_callback is None:
                # select application layer protocol
                def alpn_select_callback(conn_, options):
                    if alpn_select in options:
                        return bytes(alpn_select)
                    else:  # pragma no cover
                        return options[0]
                context.set_alpn_select_callback(alpn_select_callback)
            elif alpn_select_callback is not None and alpn_select is None:
                context.set_alpn_select_callback(alpn_select_callback)
            elif alpn_select_callback is not None and alpn_select is not None:
                raise exceptions.TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (method).")

        return context