上一篇用netty实现的http代理服务器还没法对https报文进行解密,缘由也说了,就是服务器的私钥不在咱们这,根据RSA公钥加密私钥解密的特性,若是咱们没有私钥的话是不可能获取到https的真实内容的,那有没有什么办法解密https的报文呢,固然有经过代理服务器伪造ssl证书就能够达到目的,那么具体是什么原理的,下面一步一步来分析。java
## https协议
首先来回顾下https协议的ssl握手git
注意第三步很是关键,ssl证书是采用信任链的方式来验证ssl证书是否有效,在浏览器中都会内置好许多受信任的CA证书,而CA证书下签发的ssl证书来访问浏览器才会验证经过,否则就会提示证书不安全(12306典型的例子,由于12306是本身签发的CA根证书,不存在于浏览器受信任的证书列表中,因此浏览器会提示不安全)。因此就是要制做一个服务器证书,而后还要让浏览器安全校验经过才行。github
在代理服务器拿到目标服务器ssl证书的时候,不返回给浏览器,而是使用咱们本身制做的ssl证书,这个时候有个问题,就是本身制做的证书并非浏览器中CA根证书签发的,因此咱们须要把本身制做的CA根证书加入浏览器受信任的根证书颁发机构。web
代理服务器返回的ssl证书中是由刚刚的CA证书签发的,这样就造成了一个受信任链,其实代理服务器就至关于一个浏览器访问目标目标服务器,获取到明文以后在经过客户端和代理服务器创建的ssl链接再转发给浏览器,就能够捕获并拦截到明文了。算法
生成java支持的私钥segmentfault
openssl genrsa -des3 -out ca.key 2048 openssl pkcs8 -topk8 -nocrypt -inform PEM -outform DER -in ca.key -out ca_private.pem
再经过CA私钥生成CA证书浏览器
openssl req -sha256 -new -x509 -days 365 -key ca.key -out ca.crt \ -subj "/C=CN/ST=GD/L=SZ/O=lee/OU=study/CN=ProxyeeRoot"
上面咱们就制做了一个本身的CA证书,打开ca.crt来看一看安全
这里咱们就拥有了CA根证书的私钥,只要在程序中经过这个私钥就能向下签发服务器证书了,如图代理服务器签发了一个www.baidu.com的ssl证书服务器
JAVA自带的SSL以及X509库只能使用SSL证书,不能生成SSL证书。所以咱们使用“Bouncy Castle”这个算法库来实现SSL证书的生成。websocket
//maven <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.49</version> </dependency>
//注册bouncycastle Security.addProvider(new BouncyCastleProvider()); //生成ssl证书公钥和私钥 KeyPairGenerator caKeyPairGen = KeyPairGenerator.getInstance("RSA", "BC"); caKeyPairGen.initialize(2048, new SecureRandom()); PrivateKey serverPriKey = keyPair.getPrivate(); PublicKey serverPubKey = keyPair.getPublic(); //经过CA私钥动态签发ssl证书 public static X509Certificate genCert(String issuer, PublicKey serverPubKey, PrivateKey caPriKey, String host) throws Exception { X509V3CertificateGenerator v3CertGen = new X509V3CertificateGenerator(); String issuer = "C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=ProxyeeRoot"; String subject = "C=CN, ST=GD, L=SZ, O=lee, OU=study, CN=" + host; v3CertGen.reset(); v3CertGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); v3CertGen.setIssuerDN(new X509Principal(issuer)); v3CertGen.setNotBefore(new Date(System.currentTimeMillis() - 10 * ONE_DAY)); v3CertGen.setNotAfter(new Date(System.currentTimeMillis() + 3650 * ONE_DAY)); v3CertGen.setSubjectDN(new X509Principal(subject)); v3CertGen.setPublicKey(serverPubKey); //SHA256 Chrome须要此哈希算法不然会出现不安全提示 v3CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); //SAN扩展 Chrome须要此扩展不然会出现不安全提示 GeneralNames subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, host)); v3CertGen.addExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName); X509Certificate cert = v3CertGen.generateX509Certificate(caPriKey); return cert; }
至此咱们最重要的功能已经实现了,接着就是拿着ssl证书返回给客户端便可捕获明文了。
这里我拦截了访问https://www.baidu.com的请求,并在加上了一对响应头。
这样就以动态签发ssl证书方法,解决了https明文捕获的问题。此外还添加了对websocket的支持,而且提供拦截器对外使用,实现上面效果的代码以下:
new NettyHttpProxyServer().initProxyInterceptFactory(() -> new HttpProxyIntercept() { @Override public boolean afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse) { //拦截响应,添加一个响应头 httpResponse.headers().add("intercept","test"); return true; } }).start(9999);
但愿经过一个sdk的方式把http代理服务器开源出去供你们使用,源码托管在github,欢迎start。
笔者如今用这个sdk嗅探http下载请求,而后对于支持http断点下载的文件,采用多链接分段下载,想体验能够在这里看看。固然也能够作到相似fiddler的自动替换请求体、响应体功能,具体就看你们的业务需求了!