JAVA寫HTTP代理服務(wù)器(三)-https明文捕獲

很久沒(méi)更新了,其實(shí)https明文攔截已經(jīng)實(shí)現(xiàn)很久了只是沒(méi)發(fā)博客(畢竟我太懶),步入正題吧,上一篇用netty實(shí)現(xiàn)的http代理服務(wù)器還無(wú)法對(duì)https報(bào)文進(jìn)行解密,原因也說(shuō)了,就是服務(wù)器的私鑰不在我們這,根據(jù)RSA公鑰加密私鑰解密的特性,如果我們沒(méi)有私鑰的話是不可能獲取到https的真實(shí)內(nèi)容的,那有沒(méi)有什么辦法解密https的報(bào)文呢,當(dāng)然有通過(guò)代理服務(wù)器偽造ssl證書就可以達(dá)到目的,那么具體是什么原理的,下面一步一步來(lái)分析。

https協(xié)議

首先來(lái)回顧下https協(xié)議的ssl握手

ssl握手

簡(jiǎn)單敘述下ssl握手中,只說(shuō)說(shuō)ssl單向驗(yàn)證過(guò)程

  1. 客戶端向服務(wù)器發(fā)出ssl握手,發(fā)送client-random隨機(jī)數(shù)。
  2. 服務(wù)器返回ssl證書和server-randon隨機(jī)數(shù)。
  3. 客戶端校驗(yàn)ssl證書,校驗(yàn)通過(guò),再生成一個(gè)premaster-secret隨機(jī)數(shù)用服務(wù)器證書里的公鑰加密發(fā)送,這個(gè)時(shí)候客戶端已經(jīng)可以通過(guò)三個(gè)隨機(jī)數(shù)算出對(duì)稱加密的密鑰了。
  4. 服務(wù)器用私鑰解密premaster-secret,也拿到了三個(gè)隨機(jī)數(shù)算出對(duì)稱加密的密鑰。
  5. 兩邊都用算出來(lái)的對(duì)稱密鑰進(jìn)行報(bào)文加密和解密

注意第三步非常關(guān)鍵,ssl證書是采用信任鏈的方式來(lái)驗(yàn)證ssl證書是否有效,在瀏覽器中都會(huì)內(nèi)置好許多受信任的CA證書,而CA證書下簽發(fā)的ssl證書來(lái)訪問(wèn)瀏覽器才會(huì)驗(yàn)證通過(guò),不然就會(huì)提示證書不安全(12306典型的例子,因?yàn)?2306是自己簽發(fā)的CA根證書,不存在于瀏覽器受信任的證書列表中,所以瀏覽器會(huì)提示不安全)。所以就是要制作一個(gè)服務(wù)器證書,然后還要讓瀏覽器安全校驗(yàn)通過(guò)才行。

動(dòng)態(tài)替換ssl證書

動(dòng)態(tài)替換ssl證書

在代理服務(wù)器拿到目標(biāo)服務(wù)器ssl證書的時(shí)候,不返回給瀏覽器,而是使用我們自己制作的ssl證書,這個(gè)時(shí)候有個(gè)問(wèn)題,就是自己制作的證書并不是瀏覽器中CA根證書簽發(fā)的,所以我們需要把自己制作的CA根證書加入瀏覽器受信任的根證書頒發(fā)機(jī)構(gòu)。

QQ截圖20171031155636.png

代理服務(wù)器返回的ssl證書中是由剛剛的CA證書簽發(fā)的,這樣就形成了一個(gè)受信任鏈,其實(shí)代理服務(wù)器就相當(dāng)于一個(gè)瀏覽器訪問(wèn)目標(biāo)目標(biāo)服務(wù)器,獲取到明文之后在通過(guò)客戶端和代理服務(wù)器建立的ssl連接再轉(zhuǎn)發(fā)給瀏覽器,就可以捕獲并攔截到明文了。

openssl制作CA根證書

生成java支持的私鑰

openssl genrsa -des3 -out ca.key 2048
openssl pkcs8 -topk8 -nocrypt -inform PEM -outform DER -in ca.key -out ca_private.pem

再通過(guò)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"

上面我們就制作了一個(gè)自己的CA證書,打開(kāi)ca.crt來(lái)看一看


這里寫圖片描述

這里我們就擁有了CA根證書的私鑰,只要在程序中通過(guò)這個(gè)私鑰就能向下簽發(fā)服務(wù)器證書了,如圖代理服務(wù)器簽發(fā)了一個(gè)www.baidu.com的ssl證書

QQ截圖20171031172306.png

java動(dòng)態(tài)簽發(fā)ssl證書

JAVA自帶的SSL以及X509庫(kù)只能使用SSL證書,不能生成SSL證書。因此我們使用“Bouncy Castle”這個(gè)算法庫(kù)來(lái)實(shí)現(xiàn)SSL證書的生成。

//maven
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.49</version>
</dependency>
//注冊(cè)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();
//通過(guò)CA私鑰動(dòng)態(tài)簽發(fā)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需要此哈希算法否則會(huì)出現(xiàn)不安全提示
        v3CertGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
        //SAN擴(kuò)展 Chrome需要此擴(kuò)展否則會(huì)出現(xiàn)不安全提示
        GeneralNames subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, host));
        v3CertGen.addExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
        X509Certificate cert = v3CertGen.generateX509Certificate(caPriKey);
        return cert;
    }

至此我們最重要的功能已經(jīng)實(shí)現(xiàn)了,接著就是拿著ssl證書返回給客戶端即可捕獲明文了。

效果

這里我攔截了訪問(wèn)https://www.baidu.com的請(qǐng)求,并在加上了一對(duì)響應(yīng)頭。

QQ截圖20171031173455.png

這樣就以動(dòng)態(tài)簽發(fā)ssl證書方法,解決了https明文捕獲的問(wèn)題。此外還添加了對(duì)websocket的支持,并且提供攔截器對(duì)外使用,實(shí)現(xiàn)上面效果的代碼如下:

new NettyHttpProxyServer().initProxyInterceptFactory(() -> new HttpProxyIntercept() {

    @Override
    public boolean afterResponse(Channel clientChannel, Channel proxyChannel, HttpResponse httpResponse) {
        //攔截響應(yīng),添加一個(gè)響應(yīng)頭
        httpResponse.headers().add("intercept","test");
        return true;
    }

}).start(9999);

后記

希望通過(guò)一個(gè)sdk的方式把http代理服務(wù)器開(kāi)源出去供大家使用,源碼托管在github,歡迎start。
筆者現(xiàn)在用這個(gè)sdk嗅探http下載請(qǐng)求,然后對(duì)于支持http斷點(diǎn)下載的文件,采用多連接分段下載,想體驗(yàn)可以在這里看看。
當(dāng)然也可以做到類似fiddler的自動(dòng)替換請(qǐng)求體、響應(yīng)體功能,具體就看大家的業(yè)務(wù)需求了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容