Создание HTTPS прокси-сервера в Python - PullRequest
0 голосов
/ 14 февраля 2020

Я пытаюсь создать прокси-сервер HTTPS в python, я создал следующий скрипт, который работает с HTTP.

#!/usr/bin/env python3
# coding=utf-8

import socket
from threading import Thread


class Proxy:
    def __init__(self, port=3000):
        self.port = port
        self.proxy = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.proxy.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.buffer_size = 4096

    def run(self):
        self.proxy.bind(("0.0.0.0", self.port))
        self.proxy.listen(100)
        print("  * Proxy server is running on port {}".format(self.port))

        while True:
            client, addr = self.proxy.accept()
            print(" => {}:{}".format(addr[0], addr[1]))
            Thread(target=self.handle_request, args=(client,)).start()

    def handle_request(self, client):
        head = self.parse_head(client.recv(self.buffer_size))
        headers = head["headers"]
        request = "{}\r\n".format(head["meta"])
        for key, value in headers.items():
            request += "{}: {}\r\n".format(key, value)
        request += "\r\n"
        if "content-length" in headers:
            while len(head["chunk"]) < int(headers["content-length"]):
                head["chunk"] += client.recv(self.buffer_size)

        request = request.encode() + head["chunk"]
        port = 80
        try:
            tmp = head["meta"].split(" ")[1].split("://")[1].split("/")[0]
        except IndexError:
            client.close()
            return
        if tmp.find(":") > -1:
            port = int(tmp.split(":")[1])

        response = self.send_to_server(headers["host"], port, request)
        client.sendall(response)
        client.close()


    def send_to_server(self, host, port, data):
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.connect((socket.gethostbyname(host), port))
        server.sendall(data)

        head = self.parse_head(server.recv(4096))
        headers = head["headers"]
        response = "{}\r\n".format(head["meta"])
        for key, value in headers.items():
            response += "{}: {}\r\n".format(key, value)
        response += "\r\n"

        if "content-length" in headers:
            while len(head["chunk"]) < int(headers["content-length"]):
                head["chunk"] += server.recv(self.buffer_size)

        response = response.encode() + head["chunk"]
        server.close()
        return response


    def parse_head(self, head_request):
        nodes = head_request.split(b"\r\n\r\n")
        heads = nodes[0].split(b"\r\n")
        meta = heads.pop(0).decode("utf-8")
        data = {
            "meta": meta,
            "headers": {},
            "chunk": b""
        }

        if len(nodes) >= 2:
            data["chunk"] = nodes[1]

        for head in heads:
            pieces = head.split(b": ")
            key = pieces.pop(0).decode("utf-8")
            if key.startswith("Connection: "):
                data["headers"][key.lower()] = "close"
            else:
                data["headers"][key.lower()] = b": ".join(pieces).decode("utf-8")
        return data


if __name__ == "__main__":
    proxy = Proxy(3001)
    proxy.run()

Я никогда не работал с SSL в Python, поэтому я использовал ssl модуль для создания сокета HTTPS.

#!/usr/bin/env python3
# coding=utf-8

import socket
import ssl
from threading import Thread


class Proxy:
    def __init__(self, port=3000):
        self.port = port
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.proxy = ssl.wrap_socket(self.s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers="ADH-AES256-SHA")
        self.buffer_size = 4096

    def run(self):
        self.proxy.bind(("0.0.0.0", self.port))
        self.proxy.listen(100)
        print("  * Proxy server is running on port {}".format(self.port))

        while True:
            client, addr = self.proxy.accept()
            print(" => {}:{}".format(addr[0], addr[1]))
            Thread(target=self.handle_request, args=(client,)).start()

    def handle_request(self, client):
        head = self.parse_head(client.recv(self.buffer_size))
        headers = head["headers"]
        request = "{}\r\n".format(head["meta"])
        for key, value in headers.items():
            request += "{}: {}\r\n".format(key, value)
        request += "\r\n"
        if "content-length" in headers:
            while len(head["chunk"]) < int(headers["content-length"]):
                head["chunk"] += client.recv(self.buffer_size)

        request = request.encode() + head["chunk"]
        port = 80
        try:
            tmp = head["meta"].split(" ")[1].split("://")[1].split("/")[0]
        except IndexError:
            client.close()
            return
        if tmp.find(":") > -1:
            port = int(tmp.split(":")[1])

        response = self.send_to_server(headers["host"], port, request)
        client.sendall(response)
        client.close()


    def send_to_server(self, host, port, data):
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.connect((socket.gethostbyname(host), port))
        server.sendall(data)

        head = self.parse_head(server.recv(4096))
        headers = head["headers"]
        response = "{}\r\n".format(head["meta"])
        for key, value in headers.items():
            response += "{}: {}\r\n".format(key, value)
        response += "\r\n"

        if "content-length" in headers:
            while len(head["chunk"]) < int(headers["content-length"]):
                head["chunk"] += server.recv(self.buffer_size)

        response = response.encode() + head["chunk"]
        server.close()
        return response


    def parse_head(self, head_request):
        nodes = head_request.split(b"\r\n\r\n")
        heads = nodes[0].split(b"\r\n")
        meta = heads.pop(0).decode("utf-8")
        data = {
            "meta": meta,
            "headers": {},
            "chunk": b""
        }

        if len(nodes) >= 2:
            data["chunk"] = nodes[1]

        for head in heads:
            pieces = head.split(b": ")
            key = pieces.pop(0).decode("utf-8")
            if key.startswith("Connection: "):
                data["headers"][key.lower()] = "close"
            else:
                data["headers"][key.lower()] = b": ".join(pieces).decode("utf-8")
        return data


if __name__ == "__main__":
    proxy = Proxy(3001)
    proxy.run()

Но я получаю следующую ошибку.

  * Proxy server is running on port 3001
Traceback (most recent call last):
  File ".\proxy.py", line 98, in <module>
    proxy.run()
  File ".\proxy.py", line 22, in run
    client, addr = self.proxy.accept()
  File "C:\Users\anyms\AppData\Local\Programs\Python\Python38-32\lib\ssl.py", line 1355, in accept
    newsock = self.context.wrap_socket(newsock,
  File "C:\Users\anyms\AppData\Local\Programs\Python\Python38-32\lib\ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "C:\Users\anyms\AppData\Local\Programs\Python\Python38-32\lib\ssl.py", line 1040, in _create
    self.do_handshake()
  File "C:\Users\anyms\AppData\Local\Programs\Python\Python38-32\lib\ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: HTTPS_PROXY_REQUEST] https proxy request (_ssl.c:1108)

1 Ответ

1 голос
/ 14 февраля 2020

Проблема на самом деле не связана с SSL, а вызвана неправильным пониманием того, как работает HTTP-прокси для HTTPS. Такой прокси вообще не делает SSL. Вместо этого он просто используется для создания туннеля до конечного сервера, а затем клиент создает HTTPS-соединение через этот туннель, поддерживая сквозное шифрование таким образом.

Сам туннель создается с использованием HTTP-метод СОЕДИНЕНИЯ . И это именно то, что вы получаете здесь на своем сокете SSL:

 ssl.SSLError: [SSL: HTTPS_PROXY_REQUEST] https proxy request (_ssl.c:1108)
                                          ^^^^^^^^^^^^^^^^^^^
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...