1. 程式人生 > >使用C#開發HTTP伺服器之支援HTTPS

使用C#開發HTTP伺服器之支援HTTPS

  各位朋友大家好,我是秦元培,歡迎大家關注我的部落格,我的部落格地址是http://qinyuanpei.com。本文是“使用C#開發HTTP伺服器”系列的第六篇文章,在這個系列文章中我們實現了一個基礎的Web伺服器,它支援從本地讀取靜態HTML頁面,支援GET和POST 兩種請求方式。該專案託管在我的 Github 上,專案地址為:https://github.com/qinyuanpei/HttpServer,感興趣的朋友可以前往瞭解。其間有朋友為我提供了HTTPS的PR,或許這偏離了這個系列開發HTTP伺服器的初衷,可是我們應該認識到普及HTTPS是大勢所趨。所以在今天這篇文章中,我將為大家帶來HTTPS相關知識的普及,以及如何為我們的這個Web伺服器增加HTTPS的支援。

  2017年我們聽到這樣一個聲音,蘋果將強制實施ATS,即App Transport Security。首先我們要了解的是ATS,它是蘋果為了保證應用資料在網路中安全地傳輸而制定的一種規則,其核心是鼓勵開發者使用安全的HTTPS協議和伺服器進行通訊。在此之前考慮到大量的應用還在使用HTTP協議,所以蘋果並未強制要求應用遵守這個規範,而此時蘋果發出這樣一種聲音,我們終於意識到蘋果這是在推廣HTTPS啊!無獨有偶,同樣作為科技巨頭之一的Google,宣佈在新發布的Chrome 56中會將僅支援HTTP協議的網頁標記為“不安全”。HTTPS到底是什麼呢?為什麼科技巨頭紛紛開始對它青眼有加呢?這或許要從HTTPS協議說起。

  HTTPS,即Hyper Text Transfer Protocol Over Secure Socket Layer的簡稱,是指以安全為目標的HTTP協議。我們可以將其理解為在HTTP協議的基礎上增加了安全機制,這裡的安全機制是指SSL,簡單來講HTTPS協議依然採用HTTP協議,不過它在HTTP和TCP間增加了加密/身份驗證層,因此在保證資料傳輸安全的同時,為伺服器提供了身份校驗機制。任何採用HTTPS協議的網站,均可通過瀏覽器位址列中的“鎖”標誌來檢視網站的認證資訊,或者是通過CA機構頒發的數字證書來查詢。下圖展示的是HTTPS協議中客戶端和伺服器端通訊過程:

HTTPS協議中客戶端和伺服器通訊過程

從圖中我們可以看出,在HTTPS協議中客戶端和伺服器端分為六步:

  • 客戶端請求伺服器,傳送握手訊息給伺服器。
  • 伺服器端返回客戶端加密演算法、數字證書和公鑰。
  • 客戶端對返回的數字證書進行驗證,如果驗證通過則產生一個隨機數,否則提示驗證失敗。
  • 客戶端使用公鑰對產生的隨機數進行加密,然後將其傳送給伺服器端。
  • 伺服器對該隨機數進行解密,並以此作為金鑰傳送握手資訊給客戶端。
  • 客戶端收到訊息後對訊息進行解密,如果解密成功則表示握手結束。

  這恰恰印證了我們最初的觀點,即HTTPS協議依然採用HTTP協議(三次握手)進行通訊,不同的地方在於中間環節增加了加密處理,例如在客戶端和伺服器端相互驗證的環節採用的是非對稱加密,在客戶端驗證通過以後雙方採用隨機數作為金鑰是對稱加密,而三次握手以後驗證訊息是否被篡改則是採用HASH演算法。所以我們應該可以注意到,HTTP協議和HTTPS協議的一個顯著的區別是,前者採用明文來傳輸訊息,而後者採用密文來傳輸訊息,因此HTTPS協議比HTTP協議在通訊上更為安全。而詳細來說,兩者的區別主要有:

  • HTTPS需要證書,而HTTP則不需要證書,證書由CA機構頒發。
  • HTTP採用明文來傳輸訊息,C/S端無身份驗證;HTTPS採用密文來傳輸訊息,C/S端有身份驗證。
  • HTTP預設採用80埠進行通訊,而HTTPS預設採用443埠進行通訊。

  好了,現在我們對HTTPS協議有了一個基本的認識:HTTPS協議相比HTTP協議增加了身份驗證和訊息加密的機制,因此HTTPS協議能夠保證通訊過程中的資料傳輸安全。在今天這樣一個數字時代,當個人隱私安全徹底地暴露在瀏覽器、應用程式面前,能夠提供更安全的網際網路服務無疑會讓人更有安全感,我想這是蘋果和谷歌這樣的科技巨頭公司,之所以要去努力推廣HTTPS協議的原因吧!因為客戶端需要對伺服器的證書進行驗證,所以這意味著在客戶端擁有訪問所有受信證書的能力,例如我們在使用傳統網銀產品時都需要安裝網銀證書,這其實就是為了讓客戶端在向伺服器端發起請求時方便對伺服器進行驗證,因此如果客戶端請求的URL遭遇劫持,被重定向到某個不被信任的站點上,那麼客戶端發起的請求就會被攔截。同樣的道理,伺服器端會對客戶端的請求進行驗證,所以這裡就不再詳細展開去說啦。

  我們最初設計這個HTTP伺服器的時候,沒有考慮過要支援HTTPS協議。可是當我們瞭解了HTTPS協議後,我們發現,如果要讓最初設計的Web伺服器支援HTTPS協議,我們需要關注的是Security,即身份驗證和資料加密,我們知道這裡的Security指的是SSL,所以需要了解SSL相關的內容。其次,我們需要提供一個數字證書給伺服器端,目的是在客戶端發起請求的時候,將數字證書、加密演算法和公鑰返回,保證客戶端可以完成證書校驗。從這兩點可以看出,我們首先需要從CA機構購買證書,這一點毋庸置疑。關於證書的購買及伺服器的設定,我們通過搜尋引擎可以找到相關參考。目前主流的伺服器如Apache、IIS、Tomcat和Ngnix都可以非常方便地支援HTTPS,這些問題更像是一種基礎設施,所以我會在文章末尾列舉出相關文章供大家查閱。

  這篇文章的核心是開發一個伺服器,所以在保證這些基礎設施完備的前提下,讓我們將關注點落實到程式碼上面來。我們提到,HTTPS除了證書以外關鍵點是SSL,而在.NET中提供SSL相關的API,所以這裡我們直接使用這些API就可以完成證書的建立、載入等工作。下面是相關的程式碼示例:

// 使用OpenSSL.NET生成金鑰
RSA rsa = new RSA();
BigNumber number = OpenSSL.Core.Random.Next(10, 10, 1);
rsa.GenerateKeys(1024, number, null, null);
CryptoKey key = new CryptoKey(rsa);

//建立X509證書,Subject和Issuer相同 
X509Certificate x509 = new X509Certificate();
x509.SerialNumber = (int)DateTime.Now.Ticks;
x509.Subject = new X509Name("CN=DOMAIN");        //DOMAIN為站點域名 
x509.Issuer = new X509Name("CN=DOMAIN");
x509.PublicKey = key;                            //指定公鑰 
x509.NotBefore = Convert.ToDateTime("2011-1-1"); //起始時間 
x509.NotAfter = Convert.ToDateTime("2050-1-1");  //失效時間 
x509.Version = 2;

//使用私鑰簽名
x509.Sign(key, MessageDigest.MD5);

//生成CRT證書
BIO x509bio = BIO.File("CA.crt", "w");
x509.Write(x509bio);

//生成PFX證書
var certs = new OpenSSL.Core.Stack<X509Certificate>();
PKCS12 p12 = new PKCS12("PASSWORD", key, x509, certs); //PASSWORD為保護金鑰 
BIO p12Bio = BIO.File("CA.pfx", "w");
p12.Write(p12Bio);

//載入證書
var certifiate = X509Certificate.CreateFromCertFile("CA.crt");

  在我們獲得證書以後,我們就可以通過SSL對Socket通訊過程中傳遞的訊息進行加密了,一個基本的示例程式碼如下:

SslStream sslStream = new SslStream(clientStream);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
sslStream.ReadTimeout = 10000;
sslStream.WriteTimeout = 10000;
return sslStream;

  個人感覺加密相關的問題深奧而晦澀,這篇文章中涉及到的相關概念和技術,都大大地超出了我目前的認知範圍。不過既然這位朋友熱心地提交了這個PR,我就將這個過程視為向別人的一次學習吧!我會繼續去完善這個專案:https://github.com/qinyuanpei/HttpServer。這篇部落格終於算是寫完了,週末開心!