通过 JSCH (SSH) 和 HTTPS 的反向隧道
Reverse Tunnel over JSCH (SSH) and HTTPS
我必须实现从客户端到服务器的反向隧道。我已经将 JSCH 与以下命令一起使用
session.setPortForwardingR(rport, lhost, lport);
它有效(另见 )!
接下来,我必须通过双向验证的 HTTPS 流传输我的 ssh 会话:
client -> firewall -> apache https -> ssh server
----------------------> HTTPS
====================================> SSH
---------------------->
我在找
- 一小段java代码将SSH封装成HTTPS
- 2 种 HTTPS 身份验证
- APACHE 配置
可能的解决方案:
1) HTTPS 隧道
- JHTTPTunnel, but it is based on J2ME and it doesn't support SSL (see also , )
- JOD,但不支持 SSL
3) 阿帕奇配置
- 也许这个configuration可行,但我必须尝试
## 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>
不幸的是,没有人试图回复;我找到了解决方案。
解决方案基于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
自己提出的方案还可以,我觉得是基于Implement HTTPS tunneling with JSSE
基本步骤是:
- 为 JSCH 定义连接工厂
- 打开一个 SSL 套接字并调用
"CONNECT " + host + ":" + port
在服务器端捕获所有调用 "CONNECT" 的请求并启用 22 SSH 端口。
但是你还要考虑以下问题:
- 调整超时,因为 SSL 握手时间很长
- 启用双向身份验证或所有人都可以连接到您的服务器的 22:
Using client/server certificates for two way authentication SSL socket on Android
我必须实现从客户端到服务器的反向隧道。我已经将 JSCH 与以下命令一起使用
session.setPortForwardingR(rport, lhost, lport);
它有效(另见
接下来,我必须通过双向验证的 HTTPS 流传输我的 ssh 会话:
client -> firewall -> apache https -> ssh server ----------------------> HTTPS ====================================> SSH ---------------------->
我在找
- 一小段java代码将SSH封装成HTTPS
- 2 种 HTTPS 身份验证
- APACHE 配置
可能的解决方案:
1) HTTPS 隧道
- JHTTPTunnel, but it is based on J2ME and it doesn't support SSL (see also , )
- JOD,但不支持 SSL
3) 阿帕奇配置
- 也许这个configuration可行,但我必须尝试
## 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>
不幸的是,没有人试图回复;我找到了解决方案。
解决方案基于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
自己提出的方案还可以,我觉得是基于Implement HTTPS tunneling with JSSE
基本步骤是:
- 为 JSCH 定义连接工厂
- 打开一个 SSL 套接字并调用
"CONNECT " + host + ":" + port
在服务器端捕获所有调用 "CONNECT" 的请求并启用 22 SSH 端口。
但是你还要考虑以下问题:
- 调整超时,因为 SSL 握手时间很长
- 启用双向身份验证或所有人都可以连接到您的服务器的 22: Using client/server certificates for two way authentication SSL socket on Android