Java / Network - Инкапсуляция любого протокола в http

Я пытаюсь инкапсулировать любой сетевой протокол в HTTP (я знаю, что он может работать не для всех протоколов, но это своего рода POC, который, по моему мнению, должен, наконец, работать для HTTP и HTTPS, я хотел бы заставить его работать для SSH также). Код написан на Java, основан на Apache HTTPComponents, и я назвал его JProxy. JProxy можно запустить в режиме клиента или в режиме сервера. Его цель - быть полностью прозрачным с точки зрения клиента и сервера.

Вот концепция:

  • клиент (независимо от клиента: веб-браузер, клиент ssh и т. д.) отправляет сетевые пакеты в JProxy, работающий в режиме клиента
  • JProxy инкапсулирует сетевые пакеты, полученные в запрос HTTP POST (и, конечно же, тело - это сетевые пакеты, полученные от клиента), и запрос POST отправляется JProxy, работающему в режиме сервера
  • JProxy в режиме sever получает запрос HTTP POST и извлекает тело. Затем тело отправляется конечной цели в режиме сокета.
  • Конечная цель - сервер, отвечающий отправкой сетевых пакетов обратно на JProxy сервер. Эти пакеты инкапсулируются в ответ HTTP и отправляются обратно клиенту JProxy.
  • JProxy-клиент отправляет HTTP-тело ответа исходному клиенту в режиме сокета.

Клиент JPRoxy может быть настроен для доступа к серверу JProxy через HTTP-прокси, если это необходимо.

Итак, мы имеем:

Client <-> JProxy client <-> JProxy server <-> Target

или же

Client <-> JProxy client <-> HTTP Proxy <-> JProxy server <-> Target

И протоколы:

Client <- protocol X -> JProxy client <- http -> JProxy Server <- protocol X -> Target

Все работает нормально, когда оригинальный клиент - это веб-браузер, работающий на сайтах HTTP. Но это больше не работает, когда это HTTPS (кажется, что соединение потеряно или прервано).

Вот основная часть кода (я удаляю не относящиеся к делу части).

JProxy в режиме клиента (прослушивание порта и отправка в HTTP всех полученных пакетов):

...
ServerSocket server = new ServerSocket(port, 100, InetAddress.getByName(host));
...
Socket client = server.accept();
...
client.setTcpNoDelay(true);
Thread t = new Thread(new HttpClient(client));
t.start();
...
public class HttpClient implements Runnable {
    private Socket socket;

    public HttpClient(Socket sock){
        super();
        socket = sock;
    }

    public void run() {
        // Prepare HTTP client to communicate with JProxy server
        CloseableHttpClient httpclient = creat_httpclient();

        while(!socket.isClosed()) {
            try {

                // PART I
                // Retrieve Data received from the sock server :
                socket.setSoTimeout(300);
                ByteArrayOutputStream full_buffer = new ByteArrayOutputStream();
                DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
                byte[] buffer = new byte[4096];
                int count=0;
                try {
                    while (( count = in.read(buffer)) > 0) {
                        full_buffer.write(buffer,0,count);
                    }
                } catch(java.net.SocketTimeoutException ex) {
                }

                // Send message to JProxy server
                if(full_buffer.size() > 0) {

                    if(JProxy.packet_dump)
                        FileUtils.appendBytesArrayToFile("JProxy.dump", false, full_buffer.toByteArray());

                    // PART II
                    // Scramble data to send to JProxy Server
                    /*
                    ByteArrayInputStream toSwab = new ByteArrayInputStream(full_buffer.toByteArray());
                    full_buffer.flush();
                    full_buffer.write(Swabber.runSwab(toSwab));
                    System.out.println("DEBUG SWAB IN = " + new String(full_buffer.toByteArray()));
                     */

                    // PART III
                    // Send data to JProxy HTTP Server in HTTP

                    // Post to the HTTP JProxy Server
                    HttpPost httpPost = new HttpPost("http://" + JProxy.remotHost + ":" + JProxy.remotePort);
                    HttpEntity bienEntity = EntityBuilder.create().setBinary(full_buffer.toByteArray()).build();
                    httpPost.setEntity(bienEntity);
                    full_buffer.close();
                    CloseableHttpResponse response = httpclient.execute(httpPost);

                    // PART III
                    // Get response from JProxy server :
                    HttpEntity entity = response.getEntity();

                    byte[] data;
                    if (entity == null) {
                        data = new byte [0];
                    } else {
                        data = EntityUtils.toByteArray(entity);
                    }

                    EntityUtils.consume(response.getEntity());
                    response.close();

                    if(JProxy.packet_dump)
                        FileUtils.appendBytesArrayToFile("JProxy.dump", true, data);

                    // PART IV
                    // Send response back to the original client
                    BufferedOutputStream back = new BufferedOutputStream(socket.getOutputStream());
                    back.write(data);
                    back.flush();
                    back.close();
                }
            } catch (Exception ex ) {
                ex.printStackTrace();
            }
        }

        // Socket has been closed by the original client
        try {
            httpclient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static public CloseableHttpClient creat_httpclient() {
        CloseableHttpClient httpclient = null;
        if(JProxy.proxy_host != null && JProxy.proxy_host.length() > 0) {
            Lookup<AuthSchemeProvider> authProviders = RegistryBuilder.<AuthSchemeProvider>create()
                    .register(AuthSchemes.NTLM, new NTLMSchemeFactory())                
                    .build();
            RequestConfig config = RequestConfig.custom()
                    .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.KERBEROS, AuthSchemes.SPNEGO))
                    .build();
            HttpHost proxy = new HttpHost(JProxy.proxy_host, JProxy.proxy_port);
            DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);

            if(JProxy.proxy_login != null && JProxy.proxy_login.length() > 0) {
                CredentialsProvider provider = new BasicCredentialsProvider();
                UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(JProxy.proxy_login, JProxy.proxy_password);
                provider.setCredentials(AuthScope.ANY, credentials);
                httpclient = HttpClients.custom()
                        .setDefaultAuthSchemeRegistry(authProviders)
                        .setDefaultRequestConfig(config)
                        .setDefaultCredentialsProvider(provider)
                        .setRoutePlanner(routePlanner)
                        .build();
            } else {
                httpclient = HttpClients.custom()
                    .setDefaultAuthSchemeRegistry(authProviders)
                    .setDefaultRequestConfig(config)
                    .setRoutePlanner(routePlanner)
                    .build();
            }
        } else {
            httpclient = HttpClients.createDefault();
        }
        return httpclient;
    }
}

JProxy в режиме сервера (прослушивание как веб-сервер и отправка пакетов на целевой сервер):

    public class HttpdServer {

        public HttpdServer(int port) {
            try {
                // Run listener on jproxy_port
                final IOReactorConfig config = IOReactorConfig.custom()
                        .setSoTimeout(15000)
                        .setTcpNoDelay(true)
                        .build();

                final HttpServer server = ServerBootstrap.bootstrap()
                        .setListenerPort(port)
                        .setServerInfo("JProxy/0.1")
                        .setIOReactorConfig(config)
                        .setExceptionLogger(ExceptionLogger.STD_ERR)
                        .registerHandler("*", new SocketClient())
                        .create();

                server.start();
                System.out.println("JProxy HTTP Server started on port " + port + " and targeting " + JProxy.remotHost + ":" + JProxy.remotePort);
                server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);

                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        server.shutdown(5, TimeUnit.SECONDS);
                    }
                });

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

    }

public class SocketClient implements HttpAsyncRequestHandler<HttpRequest> {

    public SocketClient() {
        super();
    }

    public void handle(final HttpRequest request, final HttpAsyncExchange httpexchange, final HttpContext httpContext) throws HttpException, IOException {
        final HttpResponse response = httpexchange.getResponse();

        // Gather what has been received by the HTTP Server
        HttpEntity entity = null;
        if (request instanceof HttpEntityEnclosingRequest)
            entity = ((HttpEntityEnclosingRequest)request).getEntity();

        byte[] data;
        if (entity == null) {
            data = new byte [0];
        } else {
            data = EntityUtils.toByteArray(entity);
        }

        if(JProxy.packet_dump)
            FileUtils.appendBytesArrayToFile("JProxy.dump", false, data);

        Socket socket = new Socket(JProxy.remotHost,JProxy.remotePort);
        socket.setSoTimeout(300);

        // Run a socket client to target the next destination
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(data);
        bos.flush();

        // Get response from final target
        ByteArrayOutputStream full_buffer = new ByteArrayOutputStream();
        DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
        byte[] buffer = new byte[4096];
        int count=0;
        try {
            while (( count = in.read(buffer)) > 0) {
                full_buffer.write(buffer,0,count);
            }
        } catch(java.net.SocketTimeoutException ex) {
            //System.out.println("DEBUG BUFFER TO SENT BACK = " + new String(full_buffer.toByteArray()));
        }
        bos.close();
        socket.close();

        if(JProxy.packet_dump)
            FileUtils.appendBytesArrayToFile("JProxy.dump", true, full_buffer.toByteArray());

        // Send response to JProxy client
        HttpEntity binaryEntity = EntityBuilder.create().setBinary(full_buffer.toByteArray()).build();
        response.setEntity(binaryEntity);
        full_buffer.close();
        httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
        EntityUtils.consume(response.getEntity());
    }

    public HttpAsyncRequestConsumer<HttpRequest> processRequest(HttpRequest arg0, HttpContext arg1) throws HttpException, IOException {
        return new BasicAsyncRequestConsumer();
    }

}

JProxy можно запустить в командной строке, например, на стороне клиента:

java -jar JProxy.jar -client 127.0.0.1:3128 -target X.X.X.X:3129

и на стороне сервера:

java -jar JProxy.jar -server 3129 -target Y.Y.Y.Y:Z

Например, давайте представим следующий сценарий:

  • локальный рабочий стол ПК с Chrome

  • удаленный прокси-сервер HTTP на сервере IP XXXX, прослушивающий порт 8118

1 / Запустите локальную JProxy:

java -jar JProxy.jar -client 127.0.0.1:3128 -target X.X.X.X:3128

2 / Запустите JProxy на удаленном сервере:

java -jar JProxy.jar -server 3128 -target 127.0.0.1:8118

(учитывая, что порт 3128 доступен через Интернет, а порт 8118 локально открыт с HTTP-прокси, таким как Privoxy или Squid)

3 / Настройте Chrome на использование HTTP-прокси с IP: 127.0.0.1 и портом 3128 и просматривайте веб-страницы

Это прекрасно работает при просмотре HTTP сайта, но с HTTPS это не работает, и я не знаю почему. Что я делаю не так? На уровне JProxy ошибки нет вообще, но в Chrome у меня есть ошибка: ERR_CONNECTION_ABORTED.

Я прочитал документацию по HTTPS, и кажется, что это туннель SSL, используемый для обмена HTTP-запросами / ответами. Так что, может быть, есть что-то делать по этому поводу, но я не знаю, что делать.

Я попытался проверить связь SSH, и вот журнал, который я собираю:

OpenSSH_7.9p1, LibreSSL 2.7.3
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 48: Applying options for *
debug1: Connecting to localhost port 2222.
debug1: Connection established.
debug1: identity file /Users/fabrice/.ssh/id_rsa type -1
debug1: identity file /Users/fabrice/.ssh/id_rsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_dsa type -1
debug1: identity file /Users/fabrice/.ssh/id_dsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_ecdsa type -1
debug1: identity file /Users/fabrice/.ssh/id_ecdsa-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_ed25519 type -1
debug1: identity file /Users/fabrice/.ssh/id_ed25519-cert type -1
debug1: identity file /Users/fabrice/.ssh/id_xmss type -1
debug1: identity file /Users/fabrice/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_7.9
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.9
debug1: match: OpenSSH_7.9 pat OpenSSH* compat 0x04000000
debug1: Authenticating to localhost:2222 as 'fabrice'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
ssh_dispatch_run_fatal: Connection to 127.0.0.1 port 2222: Broken pipe

Глядя здесь: протокол связи SSH кажется, что сервер sshd отправляет несколько пакетов в качестве ответа клиенту, и мой код сделан для получения только одного ответа.

Большое спасибо за вашу помощь.

PS: полный исходный код и скомпилированный JAR можно скачать здесь: https://mega.nz/#F!mUF0QQJA!JVBDIc44scLRq7dKo5u4GA

0 ответов

Другие вопросы по тегам