一.名词解释
代理服务器:
通常意义上说的是,通过第三方途径到达目标服务器,以及和目标服务器通信的手段。这种服务器同时具有客户端和服务端的属性,客户端用来和目标服务器交互,服务端用来和客户端交互。
Tunnel:
隧道的意思,本篇指的是基于TLS/SSL的隧道代理通道
HTTP&SOCKS v5:
HTTP和Socks v5不同于基于网络硬件的VPN技术,而是在应用层和传输层上实现的协议。对于非Tunnel模式下,前者是基于HTTP实现的透明代理,后者Socks v5直接基于TCP/UDP协议实现,也就是说,后者可以完成更多网络代理,如FTP,SSH,MAIL,SOCKET,HTTP等代理。前者可以实现的,后者完全可以实现,后者可以用在各种环境中,比如代理QQ聊天等,代理HTTP的ShadowSocks。
HTTP透明代理:
HTTP可以代理非CONNECT的GET,POST,PUT,DELETE,OPTIONS等请求,这种称为透明代理,实现起来非常容易。
SOCKS v5透明代理:
默认情况下,SOCKS v5直接通过自身协议,而不通过TLS/SSL进行数据交换的问题。
二.HTTP&SOCKS v5隧道问题
HTTP&SOCKS v5隧道实质上说的是代理HTTPS或者其他TLS/SSL的网络请求,透明代理的实现如下。
[客户端] ----------------------[代理服务器]----------------------[目标服务器]
隧道代理比透明代理多了一个环节,那就是TLS/SSL安全层密钥交换。这类有个问题就是密钥交换。
HTTP CONNECT与SOCKS v5 TLS/SSL代理如下:
[客户端] ----------<tls/ssl>-------[代理服务器]------<tls/ssl>-------[目标服务器]
问题描述:
目标服务器---->客户端 可以直接进行密钥交换,但是如果中间出现1-N个代理服务器,那么目标服务器---->客户端的密钥无法交换。同样因为证书必须是端到端的验证,因此,即便通过代理将服务端证书传递给客户端,那么依然是无效的证书。还有一个问题是TLS/SSL握手不可传递。
三.解决方案
我们不需要将目标服务器证书传递给代理服务器,我们只需要在交互的过程中,代理服务器作为服务端和客户端直接进行TLS/SSL交互,代理服务器作为客户端和目标服务器进行TLS/SSL交互,也就是说,最终目标服务器和客户端证书没有实质关联。
[ [客户端] ----------<tls/ssl - 1 >-------[代理服务器 作为SSL服务器端]]
[ [代理服务器作为SSL客户端]------<tls/ssl - 2 >-------[目标服务器]]
也就是说,最终,客户端使用的是代理服务器的证书,代理服务器使用了目标服务器的证书,两种证书没有联系,但是传输却是不可篡改的,而且是有效的。
四.实现原理
4.1代理服务器客户端通道问题的解决
最近写了一篇博客《Java 实现TLS/SSL证书的自动安装校验》,我们实现了模拟浏览器实现CA自动安装和校验。开篇我们说过,代理服务器同时具有客户端和服务器端的属性,而我们的《Java 实现TLS/SSL证书的自动安装校验》实际上用在客户端属性中。当然,我们还有另一种最简单的方法,就是全部信任所有服务器端的证书,但是这种方法的缺陷是交互性能较低。
4.2代理服务器客户端通道问题的解决
我们需要为代理服务器生成自签名的Root CA和Intermediate(中间人)CA,然后签名客户端要访问的域名。签名完成之后,升级Socket->SSLSocket,并且设置为服务端
SSLSocket secureSocket = (SSLSocket)sslFactory.createSocket(socketClient, socketClient.getInetAddress().getHostName(), socketClient.getPort(), true);
secureSocket.setUseClientMode(false);
五.附加内容
DNS Resover问题
代理实现需要频繁的域名解析,可能遇到DNS问题。
为了实现快速的域名解析,我们建议使用DNS域名解析xbill DNS,这是一个开源的DNS查询和管理工具
Lookup lookup = new Lookup("www.baidu.com",Type.A);
Record[] records = lookup.run();
System.out.println(Arrays.asList(records));
当然,如果你有自己的DNS服务器,并且对自己的DNS服务器信心满满,也可以使用如下代码段查询DNS
public static final String RECORD_A = "A";
public static List lookup(String hostName, String record) {
List result = new Vector();
try {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
//添加DNS服务器地址
env.put("java.naming.provider.url","dns://114.114.114.114");//
InitialDirContext ictx = new InitialDirContext(env);
Hashtable<?, ?> environment = ictx.getEnvironment();
System.out.println(environment);
Attributes attrs = ictx.getAttributes(hostName, new String[] { record });
Attribute attr = attrs.get(record);
NamingEnumeration attrEnum = attr.getAll();
while (attrEnum.hasMoreElements())
{
result.add(attrEnum.next());
}
} catch (NamingException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return result;
}