本篇簡介:
本篇的小目標:
挑戰通過Qt WebEngine實現與服務端的Https雙向認證
雙向認證,Qt WebEngine和Chromium
這里先說結論:挑戰失敗了。至少使用Qt WebEngine目前已實現的組件沒有辦法直接實現雙向認證。
先來簡要分析一下實現雙向認證需要做些什么。首先,服務端和客戶端——客戶端也就是我們的定制瀏覽器——各自需要向對方提供自己的安全證書,并檢查對方所提供的安全證書是否在自己的信任庫中。Qt提供了設置這樣的安全環境的組件QSslConfiguration,普通的加密通信如果要實現雙向認證,可以很方便地使用這個組件完成客戶端證書和信任庫的設置(具體可以參考我的另一篇文章:QSslSocket雙向認證設置)。
然而比較坑的地方是,就目前的5.10版而言,Qt WebEngine并不能直接載入QSslConfiguration里的設置。因為使用了Chromium作為內核,WebEngine在加載頁面時走的是Chromium自己的一條網絡棧,并沒使用Qt的安全環境設置。而Chromium在處理Https請求時,默認是直接讀取操作系統中設置的證書作為客戶端自己的安全證書,現有版本的WebEngine對此沒有進行更改。更坑的地方是,Chromium本身是提供了選擇客戶端證書的接口的,在使用Chrome瀏覽器第一次訪問需要認證客戶端的https頁面時,會彈出一個選擇客戶端證書的框,如下圖所示:
certselect.png
而WebEngine似乎會直接略過客戶端證書的加載。這個bug已經被提交到了官方,已經有相應的補丁出爐,但是要整合到發布版里可能要等到Qt5.12版本了,具體可參考:WebEngine無法載入客戶端證書的bug report。
上面所說的這個bug,直接導致了服務端無法對客戶端進行安全認證。因此就現有版本而言,是無法直接通過使用WebEngine來實現https雙向認證的。在Qt源碼中打上上面鏈接中的補丁并重新編譯Qt的話應該可以一定程度解決這個問題,這里我就不進行進一步的嘗試了。
那么反過來,客戶端可以認證服務端證書嗎?承前所述,因為WebEngine無法直接載入QSslConfiguration里的設置,同時也沒有提供查詢服務端證書信息的接口,因此客戶端也無法直接認證服務端的證書。
迂回路線?
那么,有沒有辦法繞過上面說的這些坑呢?當然有,那就是不使用WebEngine的load,而使用Qt自己的QNetworkAccessManager訪問頁面,然后將返回數據通過WebEngineView的各種setter(例如setHtml)控制WebEngine對頁面進行加載。這種方式比較繁瑣,可能會適用于某些僅訪問較為固定頁面的系統。但是這和我在這個系列開篇所講的理念就不是非常相符了,因此在這里就不展開分析了。
不用雙向認證的https?
這里再把問題簡化一下:如果服務端和客戶端不需要進行相互認證,只是希望通信是加密呢?這其實實現起來就和普通的http訪問沒有太大的區別了。
這里只稍微分析下一種比較常見的情況:服務端的證書不受信任。我們在使用chrome瀏覽器訪問證書不受信任的https網站時,會彈出“您的鏈接不是私密鏈接”的提示頁面,然后可以通過選擇繼續前往強制訪問。而WebEngine默認的實現則會直接ban掉這次訪問,并沒有提供強制訪問的選項。
好在這次WebEnginePage里提供了certificateError方法可以解決這個問題。只需要實現一個WebEnginePage的子類,并重載certificateError,就可以選擇性地忽略一些證書錯誤。下面的實現里忽略了證書不受信任的錯誤,供大家參考:
QList<:error> QSslPage::m_allowedError =
QList<:error>()
<< QWebEngineCertificateError::Error::CertificateAuthorityInvalid;
SslPage::SslPage(QObject* parent) : QWebEnginePage(parent)
{
}
bool SslPage::certificateError(const QWebEngineCertificateError& certificateError)
{
if (m_allowedError.contains(certificateError.error()))
{
return true;
}
else
{
qDebug() << "[cert error]" << certificateError.errorDescription();
return QWebEnginePage::certificateError(certificateError);
}
}
其中m_allowedError是靜態成員變量。
總結
我最初開始嘗試使用WebEngine是差不多兩年前,那個時候Qt還是5.6的版本,上面所說的Https的問題自不用說,還存在上篇文章里提到的WebChannel的嚴重bug。所以最終無奈之下,我放棄了WebEngine而采用了更底層的CEF進行開發。希望在Qt的后續版本里,WebEngine的種種問題能得以改善——畢竟是Qt自家的組件,使用起來還是更便利一些的。
寫到這里,我個人有關WebEngine的整理也進行的差不多了。下一篇開始介紹Qt整合CEF實現定制化瀏覽器的方案。
參考鏈接