上一篇用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的自動替換請求體、響應體功能,具體就看你們的業務需求了!