通过JSCH(SSH)和HTTPS反向隧道

时间:2015-05-16 11:06:51

标签: java apache ssh https http-tunneling

我必须在客户端到服务器之间实现反向隧道。我使用JSCH和以下命令

session.setPortForwardingR(rport, lhost, lport);

它有效(另见Reverse SSH tunnel with JSCH Java)!

接下来,我必须通过双向认证的HTTPS流隧道我的ssh会话:

client -> firewall -> apache https -> ssh server 

----------------------> HTTPS
====================================> SSH
---------------------->

我正在寻找

  1. 将SSH封装成HTTPS的一小段Java代码
  2. 双向HTTPS身份验证
  3. APACHE配置
  4. 可能的解决方案:

    1)HTTPS隧道

    1. JHTTPTunnel,但它基于J2ME且不支持SSL(另请参阅Java Http TunnelingIs there an Java library for sending binary data over HTTP, HTTP Tunneling?
    2. JOD,但它不支持SSL
    3. 3)APACHE CONFIGURATION

      1. 也许这configuration有效,但我必须尝试
      2. ## Load the required modules.
        LoadModule proxy_http_module modules/mod_proxy_http.so
        LoadModule proxy_connect_module modules/mod_proxy_connect.so
        
        ## Listen on port 8443 (in addition to other ports like 80 or 443)
        Listen 8443
        
        <VirtualHost *:8443>
        
          ServerName youwebserver:8443
          DocumentRoot /some/path/maybe/not/required
          ServerAdmin admin@example.com
        
          ## Only ever allow incoming HTTP CONNECT requests.
          ## Explicitly deny other request types like GET, POST, etc.
          ## This tells Apache to return a 403 Forbidden if this virtual
          ## host receives anything other than an HTTP CONNECT.
          RewriteEngine On
          RewriteCond %{REQUEST_METHOD} !^CONNECT [NC]
          RewriteRule ^/(.*)$ - [F,L]
        
          ## Setup proxying between youwebserver:8443 and yoursshserver:22
        
          ProxyRequests On
          ProxyBadHeader Ignore
          ProxyVia Full
        
          ## IMPORTANT: The AllowCONNECT directive specifies a list
          ## of port numbers to which the proxy CONNECT method may
          ## connect.  For security, only allow CONNECT requests
          ## bound for port 22.
          AllowCONNECT 22
        
          ## IMPORTANT: By default, deny everyone.  If you don't do this
          ## others will be able to connect to port 22 on any host.
          <Proxy *>
            Order deny,allow
            Deny from all
          </Proxy>
        
          ## Now, only allow CONNECT requests bound for kolich.com
          ## Should be replaced with yoursshserver.com or the hostname
          ## of whatever SSH server you're trying to connect to.  Note
          ## that ProxyMatch takes a regular expression, so you can do
          ## things like (kolich\.com|anothersshserver\.com) if you want
          ## to allow connections to multiple destinations.
          <ProxyMatch (kolich\.com)>
            Order allow,deny
            Allow from all
          </ProxyMatch>
        
          ## Logging, always a good idea.
          LogLevel warn
          ErrorLog logs/yourwebserver-proxy_error_log
          CustomLog logs/yourwebserver-proxy_request_log combined
        
        </VirtualHost>
        

2 个答案:

答案 0 :(得分:1)

你自己提出的解决方案是可以的,它基于Implement HTTPS tunneling with JSSE我认为。

基本步骤是:

  1. 为JSCH定义您的连接工厂
  2. 打开SSL套接字并致电"CONNECT " + host + ":" + port
  3. 服务器端的

    捕获所有请求调用&#34; CONNECT&#34;并启用22个SSH端口。

    但你还要考虑以下问题:

    1. 调整超时因为SSL握手很长
    2. 启用双向身份验证或所有人都可以连接到您的服务器的22个: Using client/server certificates for two way authentication SSL socket on Android

答案 1 :(得分:0)

不幸的是,没有人试图回复;我找到了解决方案。

该解决方案基于HTTP 1.1 CONNECT命令,并且不支持直接隧道。

在Java客户端

         // Install the all-trusting trust manager
         final SSLContext sc = SSLContext.getInstance("SSL");
         sc.init(null, trustAllCerts, new java.security.SecureRandom());
         JSch jsch = new JSch();
         Session session = jsch.getSession("root", "SSH-server", 22);

         session.setSocketFactory(new SocketFactory() {
          Socket tunnel = null;

          public Socket createSocket(String host, int port) throws IOException, UnknownHostException {

              SSLSocketFactory ssf = sc.getSocketFactory();

              // HTTP
              tunnel = ssf.createSocket(System.getProperty("https.proxyHost"), Integer.getInteger("https.proxyPort"));
              tunnel.setKeepAlive(true);

              doTunnelHandshake(tunnel, host, port);
              System.out.println(tunnel + " connect " + tunnel.isConnected());
              return tunnel; // dummy
          }

          public InputStream getInputStream(Socket socket) throws IOException {
              System.out.println(tunnel + " getInputStream " + socket.isConnected());
              return tunnel.getInputStream();
          }

          public OutputStream getOutputStream(Socket socket) throws IOException {
              System.out.println("getOutputStream");
              return socket.getOutputStream();
          }           });

      session.connect();

      try {
          session.setPortForwardingR(3391, "localhost", 3389);
      ....

,其中

  private static void doTunnelHandshake(Socket tunnel, String host, int port) throws IOException {
        OutputStream out = tunnel.getOutputStream();
        String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n" + 
       "User-Agent: " +
       sun.net.www.protocol.http.HttpURLConnection.userAgent + "\r\n\r\n";
        byte b[];
        try {

              b = msg.getBytes("ASCII7");
        } catch (UnsupportedEncodingException ignored) {
              /*
               * If ASCII7 isn't there, something serious is wrong, but Paranoia
               * Is Good (tm)
               */
              b = msg.getBytes();
        }
        out.write(b);
        out.flush();

        /*
         * We need to store the reply so we can create a detailed error message
         * to the user.
         */
        byte reply[] = new byte[200];
        int replyLen = 0;
        int newlinesSeen = 0;
        boolean headerDone = false; /* Done on first newline */

        InputStream in = tunnel.getInputStream();
        boolean error = false;

        while (newlinesSeen < 2) {
              int i = in.read();
              if (i < 0) {
                    throw new IOException("Unexpected EOF from proxy");
              }
              if (i == '\n') {
                    headerDone = true;
                    ++newlinesSeen;
              } else if (i != '\r') {
                    newlinesSeen = 0;
                    if (!headerDone && replyLen < reply.length) {
                          reply[replyLen++] = (byte) i;
                    }
              }
        }

        /*
         * Converting the byte array to a string is slightly wasteful in the
         * case where the connection was successful, but it's insignificant
         * compared to the network overhead.
         */
        String replyStr;
        try {
              replyStr = new String(reply, 0, replyLen, "ASCII7");
        } catch (UnsupportedEncodingException ignored) {
              replyStr = new String(reply, 0, replyLen);
        }

        System.out.println(replyStr);

        /* We asked for HTTP/1.0, so we should get that back */
        if (!replyStr.startsWith("HTTP/1.0 200")) {
              throw new IOException("Unable to tunnel for " + host + ":" + port + ".  Proxy returns \"" + replyStr + "\"");
        }

        /* tunneling Handshake was successful! */
  }

在apache配置

添加ssl支持

 SSLEngine on
 SSLCertificateFile "conf/ssl.crt/server.crt"
 SSLCertificateKeyFile "conf/ssl.key/server.key"

这里是结果

Connecting to localhost port 22
HTTP/1.0 200 Connection Established
....
Authentications that can continue: password
Next authentication method: password
Authentication succeeded (password).
Connected