如何用雙向HTTPS進行“偷懶”
作者簡介:
四拾一,個推高階開發工程師,精通Java、Go語言,全棧踐行者,對於資料安全、密碼學有較深入的理解與實踐。
01
寫在前面
近期某專案有一個業務拓展的需求,需要將專案中單機房部署的模組擴充套件成異地多機房部署。原先專案的模組都部署在自建的機房A,有防火牆等相關安全策略的保護,相對比較安全,但現在網路跨越了兩個公網通訊的機房,該如何保證傳輸安全和訪問控制呢?
HTTPS可以對伺服器進行身份認證,同時也可以保證資料流量傳輸的安全,避免中間人攻擊,但這並不能滿足我們訪問許可權控制的要求(我們的服務並不希望任何人都能訪問)。
解決以上問題有兩種思路,一種是在應用層對模組進行改造,使其支援訪問許可權控制;另外一種則是雙向HTTPS,基於雙向HTTPS的特性來實現。出於是否能快速實現與成本考慮,我們最終選擇了雙向HTTPS來實現這一需求。
下面我們對如何實現這個需求與雙向HTTPS的原理做一個簡要介紹,希望給遇到類似問題的開發者提供一個思路供參考。
02
HTTPS & 雙向HTTPS
HTTPS
HTTPS 全稱為 HyperText Transfer Protocol Secure,在HTTP的基礎上,集成了TLS/SSL傳輸層協議,以提供對網站伺服器的身份認證,保護交換資料的隱私與完整性。
雙向HTTPS
雙向HTTPS在單向HTTPS認證的基礎上,增加了服務端對客戶端身份認證的步驟,在進行通訊前互相驗證對方身份,增加了網路的安全性。
03
方案思考
網路環境介紹
服務端與客戶端原先在同一機房內,只使用了HTTP協議來做資料傳輸。
我們的目標方案則是保證跨域公網的兩個模組能夠互相通訊,並在此基礎上確保輸過程的安全性與許可權控制。
解決思路
- 為服務端增加Nginx做反向HTTPS代理
此時客戶端訪問服務端的流量採用了HTTPS協議,保證了資料流量的安全,但暴露在公網上的服務端依然有被其他人訪問的風險
- 在1 的基礎上為客戶端增加Nginx做正向代理,將單向HTTPS升級為雙向HTTPS
雙向HTTPS在單向HTTPS的基礎上,多了服務端校驗客戶端證書的步驟。若服務端校驗客戶端證書失敗,則在HTTPS握手階段服務端就將其拒絕。這樣,就一定程度上實現了服務端的訪問許可權控制。
涉及新增修改的內容
-
新增代理層
代理層包括客戶端和服務端的兩個代理伺服器,此處選用Nginx -
新增一個CA中心
新增的CA中心主要用於為客戶端頒發證書( 服務端的HTTPS證書將選用商用CA證書)
配置雙向HTTPS的整體投入的整體投入和為模組開發許可權功能比起來,此方案的實現相對來說更簡單也更快捷。
環境搭建驗證
此處將使用openssl 與docker,在本地搭建一套方案中的模擬環境來驗證方案的可行性。
證書生成
證書結構
方案所需的證書結構如下:
• 生成證書
`# 生成私鑰
openssl genrsa -out ca.key 4096
生成自簽名根證書
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt`
• 生成服務端用證書
`
生成私鑰
openssl genrsa -out server.key 4096
生成CSR證書請求
openssl req -new -key server.key -out server.csr
使用CA1根證書籤發證書
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650`
• 生成客戶端證書的過程同生成服務端的過程相同,更換相應名稱即可
配置服務端Nginx
• 配置並啟動
修改服務端 Nginx配置檔案並啟動,具體關鍵配置如下:
•服務端Nginx啟動
docker run \ --name nginx_server \ # 指定對映的埠 -p 30443:30443 \ -d \ #將相應的證書和配置檔案掛載入容器 -v /Users/zhangchenyu/Documents/temp/nginx_test2/server/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/server/conf/index.html:/etc/nginx/html/index.html:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/ca1:/etc/nginx/ca1:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/ca2:/etc/nginx/ca2:ro \ nginx
使用curl命令檢查Nginx是否已經啟用雙向認證
證書註冊時使用了域名,將註冊時的相關域名新增到 /etc/hosts,否則使用本地ip無法訪問。
• 直接訪問
curl \ # 指定服務端證書的CA證書 --cacert ../ca1/ca.crt \ https://test.server:30443
服務端返回的結果提示:要求的證書未傳送。
• 指定客戶端傳送證書
curl \ # 指定服務端證書的CA證書 --cacert ../ca1/ca.crt \ # 指定客戶端證書 --cert client.crt \ # 指定客戶端私鑰 --key ./client.key \ # 指定tls 協議版本 --tlsv1.2 \ https://test.server:30443
• 呼叫結果:
當我們看到服務端返回了相應的頁面,說明服務端已經開始使用雙向HTTPS,對接收到的請求不再全部接受,而是在HTTPS握手階段要求客戶端傳送客戶端證書進行校驗,校驗通過的請求才進行處理。
配置客戶端Nginx的正向代理
配置並啟動
•客戶端Nginx配置見下
注意:docker容器內的Nginx 在配置轉發地址,指定的IP埠需要為容器內部IP埠。
• 客戶端Nginx 啟動
docker run \ --name nginx_server \ # 指定對映的埠 -p 30443:30443 \ -d \ #將相應的證書和配置檔案掛載入容器 -v /Users/zhangchenyu/Documents/temp/nginx_test2/server/conf/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/server/conf/index.html:/etc/nginx/html/index.html:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/ca1:/etc/nginx/ca1:ro \ -v /Users/zhangchenyu/Documents/temp/nginx_test2/ca2:/etc/nginx/ca2:ro \ nginx
使用curl命令檢查Nginx是否已經啟用雙向認證
證書註冊時使用了域名,將註冊時的相關域名新增到 /etc/hosts,否則使用本地ip無法訪問。
• 直接訪問
curl \ # 指定服務端證書的CA證書 --cacert ../ca1/ca.crt \ https://test.server:30443
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bMdTUHyK-1602177408571)(/img/bVceVRD)] ](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81b17287c3904c7b8501403bff5033f5~tplv-k3u1fbpfcp-zoom-1.image)
服務端返回的結果提示:要求的證書未傳送。
• 指定客戶端傳送證書
curl \ # 指定服務端證書的CA證書 --cacert ../ca1/ca.crt \ # 指定客戶端證書 --cert client.crt \ # 指定客戶端私鑰 --key ./client.key \ # 指定tls 協議版本 --tlsv1.2 \ https://test.server:30443
• 呼叫結果:
顯然,客戶端使用HTTP就訪問到了終端服務。通過以上步驟我們可以看到Nginx 的兩次代理,已經完全實現了本次需求。同時,我們發現通過Nginx配置的修改即可實現介面許可權的控制,且保證網路傳輸的安全。
抓包分析
最後,完成了配置也別忘了總結分析哦。我們對雙向HTTPS的握手和互動的過程進行抓包,抓包的同時簡要分析單雙向HTTPS的差異,並對雙向HTTPS實現許可權的控制過程予以瞭解。
• 單向HTTPS流量分析
隨意訪問一個HTTPS網站,並抓包。具體內容見下圖 :
-
客戶端向服務端傳送 Client Hello,其中包含一個隨機數A、支援的TLS版本、支援的加密套件等
-
伺服器響應給客戶端一個隨機數B、選用的TLS協議版本、選用的加密套件、伺服器證書、DH公鑰等
此處Server Hello的包和證書、服務端DH公鑰等響應的資料包分成了兩個,而雙向HTTPS的資料包是單個的,這與單向雙向無關,具體看伺服器對HTTPS協議的實現方式。
- 客戶端返回DH公鑰
-
使用DH演算法計算生成Pre-master secret,並通過Master Secret生成器及隨機數A、隨機數B、Pre-master Secret 生成最終加密通訊所用的Master Secret
-
客戶端和服務端互相告知對方自己狀態切換完成,併發送一條加密資訊,以互相驗證雙方都擁有了正確的Master Secret
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-kmfjGGkB-1602177408581)(/img/bVceVTZ)]](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9dbb16403e284efe846b8403c8a5fc5c~tplv-k3u1fbpfcp-zoom-1.image)
- 握手完成,開始使用 Master Secret加密通訊
梳理下整體流程:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-XLTZN0JQ-1602177408584)(/img/bVceVT2)]](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7cc2797f57a24017b1ea9f1456c1b87b~tplv-k3u1fbpfcp-zoom-1.image)
• 雙向HTTPS流量分析
此處抓包的內容源自兩臺Nginx之間的流量
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wk6KMA51-1602177408586)(/img/bVceVT4)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/91b8a4c75bfb416980c30802e96f60a7~tplv-k3u1fbpfcp-zoom-1.image)
客戶端向服務端傳送 Client Hello,其中包含隨機數A、支援的TLS版本、支援的加密套件等
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JlfnJMlB-1602177408592)(/img/bVceVT8)]](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ed4f67c983e14fe8ae1f8722567b5aec~tplv-k3u1fbpfcp-zoom-1.image)
加密套件說明 例如: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
ECDHE 祕鑰交換演算法
RSA 身份驗證演算法
AES128_GCM 批量加密演算法
SHA256 訊息認證碼演算法
- 伺服器響應給客戶端隨機數B、選用的TLS協議版本、選用的加密套件、伺服器證書、DH演算法公鑰、以及要求客戶端返回證書的請求等
– 隨機數B、選用的TLS協議版本、選用的加密套件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zGyzeK6u-1602177408594)(/img/bVceVT9)]](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2780ecb32f444d4ab74794fe707f0625~tplv-k3u1fbpfcp-zoom-1.image)
– 服務端證書
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-f54AhHh9-1602177408595)(/img/bVceVUc)]
](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5cc40903089d417eaa9d95aa58be2196~tplv-k3u1fbpfcp-zoom-1.image)
– 服務端DH公鑰
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-RfOQjTYh-1602177408597)(/img/bVceVUa)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/940cb04a5be242bfa6ee99c59d28b97b~tplv-k3u1fbpfcp-zoom-1.image)
ServerKeyExchange是隻有DH祕鑰交換演算法才有的一步,可以理解為這一步是為了計算得到Pre-master Secret;通過非對稱加密方式來握手獲取Pre-master Secret的加密套件不需要這一步。
– 要求客戶端返回證書的請求
-
客戶端校驗證書的有效性
此處操作由Nginx 客戶端內部完成,未體現在抓包中。 -
客戶端返回客戶端自身證書, 以及對證書的簽名後, 通知服務端自己的加密策略已轉換,以及第一條加密資訊(用協商出的加密祕鑰加密)
– 客戶端證書
此處區別於單向HTTPS,客戶端傳送的證書會被服務端Nginx配置的根證書進行校驗,只有驗證通過的客戶端才可進行下一步。
– 客戶端DH演算法公鑰
ClientKeyExchange,同ServerKeyExchange類似,都是為了支援DH交換祕鑰
– 對證書的簽名
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7opxHbuw-1602177408609)(/img/bVceVUJ)]](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1c2dbe864248448782ca774a106c3b08~tplv-k3u1fbpfcp-zoom-1.image)
客戶端為了證明發出去的證書是自己的,需要使用私鑰對證書進行簽名,以確認證書身份。
– 通知服務端自己的加密策略已轉換(Client)
– Encrypted Handshake Message 客戶端第一條使用Master Secret加密的資料
Master Secret = MasterSecret生成器(隨機數A、 隨機數B、 DH交換獲得的Pre-master Secret)
此訊息發給服務端後,服務端會使用生成的Master Secret 進行解密,確認客戶端已生成正確的Master Secret
- 服務端返回Session Ticket、通知客戶端自己的加密策略已轉換以及第一條使用Master Secret加密的資料
– Session Ticket
服務端會快取Master Secret 一段時間,只需要客戶端將Session Ticket 帶過來,可以避免重複握手導致的資源開銷
– 通知服務端自己的加密策略已轉換(Server)
– 服務端返回第一條使用Master Secret加密的資料,功能等同於服務端傳送Encrypted Handshake Message,此項給客戶端是為了向客戶端證明自己也生成了正確的Master Secret。
- 開始使用Master Secret 加密資料並開始通訊
梳理下整體流程:
• 流程對比
我們結合整體抓包和分析的流程可知,雙向HTTPS和單向HTTPS相比,多了對客戶端證書校驗、以及相應支援處理的步驟。客戶端只有拿到了服務端CA頒發的證書,才能訪問到服務端,這也是雙向HTTPS擁有一定許可權控制功能的基礎。
04
總結
前文提及的改造需求,如果按照常規思路選擇改動業務程式碼新增許可權控制功能,需要改動的整體流程較為複雜,開發成本也相對較重,為此我們借鑑雙向HTTPS的策略,通過修改配置方式快速地實現了該需求,避免了相關許可權控制的重複勞動。
此外,HTTPS整體流程,無論是單向還是雙向,都是網際網路技術領域的基礎保障,值得我們開發者繼續探究和學習其協議的相關細節。