Esempio n. 1
0
 def on_handshake_error(self, err: str) -> layer.CommandGenerator[None]:
     self.conn.error = err
     if self.conn == self.context.client:
         yield TlsFailedClientHook(
             TlsData(self.conn, self.context, self.tls))
     else:
         yield TlsFailedServerHook(
             TlsData(self.conn, self.context, self.tls))
     yield from super().on_handshake_error(err)
Esempio n. 2
0
    def make_server_conn(tls_start: TlsData) -> None:
        # ssl_context = SSL.Context(Method.TLS_METHOD)
        # ssl_context.set_min_proto_version(SSL.TLS1_3_VERSION)
        ssl_context = SSL.Context(SSL.SSLv23_METHOD)
        ssl_context.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_TLSv1
                                | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1_2)
        ssl_context.load_verify_locations(cafile=tlsdata.path(
            "../../net/data/verificationcerts/trusted-root.crt"))
        if alpn is not None:
            ssl_context.set_alpn_protos([alpn])
        ssl_context.set_verify(SSL.VERIFY_PEER)

        tls_start.ssl_conn = SSL.Connection(ssl_context)
        tls_start.ssl_conn.set_connect_state()
        # Set SNI
        tls_start.ssl_conn.set_tlsext_host_name(tls_start.conn.sni.encode())

        # Manually enable hostname verification.
        # Recent OpenSSL versions provide slightly nicer ways to do this, but they are not exposed in
        # cryptography and likely a PITA to add.
        # https://wiki.openssl.org/index.php/Hostname_validation
        param = SSL._lib.SSL_get0_param(tls_start.ssl_conn._ssl)
        # Common Name matching is disabled in both Chrome and Firefox, so we should 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, tls_start.conn.sni.encode(), 0) == 1)
Esempio n. 3
0
 def tls_start_server(tls_start: tls.TlsData):
     # INSECURE
     ssl_context = SSL.Context(SSL.SSLv23_METHOD)
     tls_start.ssl_conn = SSL.Connection(ssl_context)
     tls_start.ssl_conn.set_connect_state()
     if tls_start.context.client.sni is not None:
         tls_start.ssl_conn.set_tlsext_host_name(tls_start.context.client.sni.encode())
Esempio n. 4
0
 def tls_start_client(tls_start: tls.TlsData):
     # INSECURE
     ssl_context = SSL.Context(SSL.SSLv23_METHOD)
     ssl_context.use_privatekey_file(
         pkg_data.path("../test/mitmproxy/data/verificationcerts/trusted-leaf.key")
     )
     ssl_context.use_certificate_chain_file(
         pkg_data.path("../test/mitmproxy/data/verificationcerts/trusted-leaf.crt")
     )
     tls_start.ssl_conn = SSL.Connection(ssl_context)
     tls_start.ssl_conn.set_accept_state()
Esempio n. 5
0
    def tls_start_client(self, tls_start: tls.TlsData) -> None:
        """Establish TLS between client and proxy."""
        if tls_start.ssl_conn is not None:
            return  # a user addon has already provided the pyOpenSSL context.

        client: connection.Client = tls_start.context.client
        server: connection.Server = tls_start.context.server

        entry = self.get_cert(tls_start.context)

        if not client.cipher_list and ctx.options.ciphers_client:
            client.cipher_list = ctx.options.ciphers_client.split(":")
        # don't assign to client.cipher_list, doesn't need to be stored.
        cipher_list = client.cipher_list or DEFAULT_CIPHERS

        if ctx.options.add_upstream_certs_to_client_chain:  # pragma: no cover
            # exempted from coverage until https://bugs.python.org/issue18233 is fixed.
            extra_chain_certs = server.certificate_list
        else:
            extra_chain_certs = []

        ssl_ctx = net_tls.create_client_proxy_context(
            min_version=net_tls.Version[ctx.options.tls_version_client_min],
            max_version=net_tls.Version[ctx.options.tls_version_client_max],
            cipher_list=tuple(cipher_list),
            cert=entry.cert,
            key=entry.privatekey,
            chain_file=entry.chain_file,
            request_client_cert=False,
            alpn_select_callback=alpn_select_callback,
            extra_chain_certs=tuple(extra_chain_certs),
            dhparams=self.certstore.dhparams,
        )
        tls_start.ssl_conn = SSL.Connection(ssl_ctx)

        # Force HTTP/1 for secure web proxies, we currently don't support CONNECT over HTTP/2.
        # There is a proof-of-concept branch at https://github.com/mhils/mitmproxy/tree/http2-proxy,
        # but the complexity outweighs the benefits for now.
        if len(tls_start.context.layers) == 2 and isinstance(
                tls_start.context.layers[0], modes.HttpProxy):
            client_alpn: Optional[bytes] = b"http/1.1"
        else:
            client_alpn = client.alpn

        tls_start.ssl_conn.set_app_data(
            AppData(
                client_alpn=client_alpn,
                server_alpn=server.alpn,
                http2=ctx.options.http2,
            ))
        tls_start.ssl_conn.set_accept_state()
Esempio n. 6
0
    def start_tls(self) -> layer.CommandGenerator[None]:
        assert not self.tls

        tls_start = TlsData(self.conn, self.context)
        if self.conn == self.context.client:
            yield TlsStartClientHook(tls_start)
        else:
            yield TlsStartServerHook(tls_start)
        if not tls_start.ssl_conn:
            yield commands.Log(
                "No TLS context was provided, failing connection.", "error")
            yield commands.CloseConnection(self.conn)
            return
        assert tls_start.ssl_conn
        self.tls = tls_start.ssl_conn
Esempio n. 7
0
    def make_client_conn(tls_start: TlsData) -> None:
        # ssl_context = SSL.Context(Method.TLS_METHOD)
        # ssl_context.set_min_proto_version(SSL.TLS1_3_VERSION)
        ssl_context = SSL.Context(SSL.SSLv23_METHOD)
        ssl_context.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_TLSv1
                                | SSL.OP_NO_TLSv1_1 | SSL.OP_NO_TLSv1_2)
        ssl_context.use_privatekey_file(
            tlsdata.path("../../net/data/verificationcerts/trusted-leaf.key"))
        ssl_context.use_certificate_chain_file(
            tlsdata.path("../../net/data/verificationcerts/trusted-leaf.crt"))
        if alpn is not None:
            ssl_context.set_alpn_select_callback(lambda conn, protos: alpn)

        tls_start.ssl_conn = SSL.Connection(ssl_context)
        tls_start.ssl_conn.set_accept_state()
Esempio n. 8
0
    def tls_start_server(self, tls_start: tls.TlsData) -> None:
        """Establish TLS between proxy and server."""
        if tls_start.ssl_conn is not None:
            return  # a user addon has already provided the pyOpenSSL context.

        client: connection.Client = tls_start.context.client
        server: connection.Server = tls_start.context.server
        assert server.address

        if ctx.options.ssl_insecure:
            verify = net_tls.Verify.VERIFY_NONE
        else:
            verify = net_tls.Verify.VERIFY_PEER

        if server.sni is None:
            server.sni = client.sni or server.address[0]

        if not server.alpn_offers:
            if client.alpn_offers:
                if ctx.options.http2:
                    # We would perfectly support HTTP/1 -> HTTP/2, but we want to keep things on the same protocol
                    # version. There are some edge cases where we want to mirror the regular server's behavior
                    # accurately, for example header capitalization.
                    server.alpn_offers = tuple(client.alpn_offers)
                else:
                    server.alpn_offers = tuple(x for x in client.alpn_offers
                                               if x != b"h2")
            else:
                # We either have no client TLS or a client without ALPN.
                # - If the client does use TLS but did not send an ALPN extension, we want to mirror that upstream.
                # - If the client does not use TLS, there's no clear-cut answer. As a pragmatic approach, we also do
                #   not send any ALPN extension in this case, which defaults to whatever protocol we are speaking
                #   or falls back to HTTP.
                server.alpn_offers = []

        if not server.cipher_list and ctx.options.ciphers_server:
            server.cipher_list = ctx.options.ciphers_server.split(":")
        # don't assign to client.cipher_list, doesn't need to be stored.
        cipher_list = server.cipher_list or DEFAULT_CIPHERS

        client_cert: Optional[str] = None
        if ctx.options.client_certs:
            client_certs = os.path.expanduser(ctx.options.client_certs)
            if os.path.isfile(client_certs):
                client_cert = client_certs
            else:
                server_name: str = server.sni or server.address[0]
                p = os.path.join(client_certs, f"{server_name}.pem")
                if os.path.isfile(p):
                    client_cert = p

        ssl_ctx = net_tls.create_proxy_server_context(
            min_version=net_tls.Version[ctx.options.tls_version_client_min],
            max_version=net_tls.Version[ctx.options.tls_version_client_max],
            cipher_list=tuple(cipher_list),
            verify=verify,
            hostname=server.sni,
            ca_path=ctx.options.ssl_verify_upstream_trusted_confdir,
            ca_pemfile=ctx.options.ssl_verify_upstream_trusted_ca,
            client_cert=client_cert,
            alpn_protos=tuple(server.alpn_offers),
        )

        tls_start.ssl_conn = SSL.Connection(ssl_ctx)
        if server.sni:
            try:
                ipaddress.ip_address(server.sni)
            except ValueError:
                tls_start.ssl_conn.set_tlsext_host_name(server.sni.encode())
            else:
                # RFC 6066: Literal IPv4 and IPv6 addresses are not permitted in "HostName".
                # It's not really ideal that we only enforce that here, but otherwise we need to add checks everywhere
                # where we assign .sni, which is much less robust.
                pass
        tls_start.ssl_conn.set_connect_state()
Esempio n. 9
0
    def receive_handshake_data(
            self,
            data: bytes) -> layer.CommandGenerator[Tuple[bool, Optional[str]]]:
        # bio_write errors for b"", so we need to check first if we actually received something.
        if data:
            self.tls.bio_write(data)
        try:
            self.tls.do_handshake()
        except SSL.WantReadError:
            yield from self.tls_interact()
            return False, None
        except SSL.Error as e:
            # provide more detailed information for some errors.
            last_err = e.args and isinstance(
                e.args[0], list) and e.args[0] and e.args[0][-1]
            if last_err == ('SSL routines', 'tls_process_server_certificate',
                            'certificate verify failed'):
                verify_result = SSL._lib.SSL_get_verify_result(
                    self.tls._ssl)  # type: ignore
                error = SSL._ffi.string(
                    SSL._lib.X509_verify_cert_error_string(
                        verify_result)).decode()  # type: ignore
                err = f"Certificate verify failed: {error}"
            elif last_err in [('SSL routines', 'ssl3_read_bytes',
                               'tlsv1 alert unknown ca'),
                              ('SSL routines', 'ssl3_read_bytes',
                               'sslv3 alert bad certificate')]:
                assert isinstance(last_err, tuple)
                err = last_err[2]
            elif last_err == ('SSL routines', 'ssl3_get_record',
                              'wrong version number') and data[:4].isascii():
                err = f"The remote server does not speak TLS."
            elif last_err == ('SSL routines', 'ssl3_read_bytes',
                              'tlsv1 alert protocol version'):
                err = (
                    f"The remote server and mitmproxy cannot agree on a TLS version to use. "
                    f"You may need to adjust mitmproxy's tls_version_server_min option."
                )
            else:
                err = f"OpenSSL {e!r}"
            return False, err
        else:
            # Here we set all attributes that are only known *after* the handshake.

            # Get all peer certificates.
            # https://www.openssl.org/docs/man1.1.1/man3/SSL_get_peer_cert_chain.html
            # If called on the client side, the stack also contains the peer's certificate; if called on the server
            # side, the peer's certificate must be obtained separately using SSL_get_peer_certificate(3).
            all_certs = self.tls.get_peer_cert_chain() or []
            if self.conn == self.context.client:
                cert = self.tls.get_peer_certificate()
                if cert:
                    all_certs.insert(0, cert)

            self.conn.timestamp_tls_setup = time.time()
            self.conn.alpn = self.tls.get_alpn_proto_negotiated()
            self.conn.certificate_list = [
                certs.Cert.from_pyopenssl(x) for x in all_certs
            ]
            self.conn.cipher = self.tls.get_cipher_name()
            self.conn.tls_version = self.tls.get_protocol_version_name()
            if self.debug:
                yield commands.Log(
                    f"{self.debug}[tls] tls established: {self.conn}", "debug")
            if self.conn == self.context.client:
                yield TlsEstablishedClientHook(
                    TlsData(self.conn, self.context, self.tls))
            else:
                yield TlsEstablishedServerHook(
                    TlsData(self.conn, self.context, self.tls))
            yield from self.receive_data(b"")
            return True, None