現在基本大家都在使用HTTPS,比如REST API, 或者其他類似的SOAP啥的。因爲HTTP是明文傳輸太不安全了。使用HTTPS會有個比較麻煩的問題就是證書的處理。這裏有幾種證書的問題:
1. 如果判斷HTTPS中的證書是否是正確的?所謂的正確有2種意思:
a. 證書是假的,並非權威機構簽發的,比如自己做的測試證書。
b. 證書也是權威機構簽發的,但並不是自己公司的。比如某個人向verisign公司買了一個證書,然後通過域名劫持啥的把某https站點引導到自己的服務器,並且使用自己購買的證書,從而收集用戶的密碼啥的。
2. 通常在開發階段用的都是測試證書,那麼很多https訪問庫,一看這個證書並不在當前機器的信任區,可能會直接丟出錯誤。
對於這些問題,其實解決起來還是蠻簡單的,基本思路就是:獲取證書信息,然後自己根據需求來進行處理。
這裏使用C#做一個簡單演示:
static public string ReadHTTPorHTTPSWithGet(string URL)
{
try
{
// combine HTTP request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
// HTTP request header
request.Method = "get";
request.ContentType = "text/plain,charset=UTF-8";
// try to get response from HTTP/HTTPS
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream ss = response.GetResponseStream();
using (StreamReader reader = new StreamReader(ss, Encoding.UTF8))
{
string data = reader.ReadToEnd();
return data;
}
}
catch (Exception e)
{
string err = e.Message;
}
return "";
}
如果使用上面的代碼來訪問https://www.baidu.com,沒有問題可以成功得到結果,因爲百度的證書是正式的,它是由verisign公司簽發的,而verisign公司的根證書都是默認在機器的信任區的,大家可以打開證書管理器看一下(certmgr.msc)
可以看到這裏有一堆的信任根證書,其中就有verisign公司的根證書(verisign公司是權威的,世界上用的大多數證書都是他們簽發的,當然微軟,蘋果,谷歌等大公司也有一些權威根證書)。
百度的證書也是verisign公司簽發的,所以沒有問題。
我們可以創建來看一下百度的證書。爲了查看百度的證書,需要自己提供一個System.Net.ICertificatePolicy的子類,重寫虛函數
public bool CheckValidationResult(ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
WebRequest req, int problem)
如:
public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
{
public TrustAllCertificatePolicy()
{ }
public bool CheckValidationResult(ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
WebRequest req, int problem)
{
System.Console.WriteLine("name: " + cert.Subject);
System.Console.WriteLine("issuer name: " + cert.Issuer);
return true;
}
}
我們可以打出證書信息。
在訪問https之前,替換成我們自己的類實例:
System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
運行一下,就可以看到百度的證書信息:
可以看到百度的證書確實是verisign公司簽發的,而verisign公司的根證書在信任區。所以驗證沒問題。
那麼如果是自己的測試證書呢?
如果我們不提供自己的certificatePolicy類,那麼怕是會丟出異常,說是證書不合法啥的。
其實原因很簡單,因爲默認的CertificatePolicy類會去檢查證書是不是合法機構簽發的。看上去類似:
public bool CheckValidationResult(ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
WebRequest req, int problem)
{
System.Console.WriteLine("name: " + cert.Subject);
System.Console.WriteLine("issuer name: " + cert.Issuer);
if (cert is in trusted area) return true;
else return false;
}
然後就出錯了。
如果我們自己重寫了CheckValidationResult,如:
public bool CheckValidationResult(ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
WebRequest req, int problem)
{
System.Console.WriteLine("name: " + cert.Subject);
System.Console.WriteLine("issuer name: " + cert.Issuer);
return true;
}
如何什麼證書都返回true那就ok了。如果我們在測試開發階段使用測試證書,那麼就可以這麼做。然後等正式發佈的時候(有正式證書了),就把這段代碼去掉,這樣程序就只認權威機構簽發的證書了。另外還有個問題,如果爲了防止域名被劫持,然後使用一個權威機構簽發的另外一個證書。解決問題也很簡單,就是重寫CheckValidationResult函數,然後在裏面得到證書的信息,看一下subject裏面是不是自己公司的,如果是就返回true,否則返回false,這樣就防止公司的https被中途攔截了。
完整代碼:
這個例子裏實現了GET和POST。可以直接運行,並且看百度的證書信息。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace TestHTTPS
{
public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
{
public TrustAllCertificatePolicy()
{ }
public bool CheckValidationResult(ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
WebRequest req, int problem)
{
System.Console.WriteLine("name: " + cert.Subject);
System.Console.WriteLine("issuer name: " + cert.Issuer);
return true;
}
}
class Program
{
static public string ReadHTTPorHTTPSWithGet(string URL)
{
try
{
System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
// combine HTTP request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
// HTTP request header
request.Method = "get";
request.ContentType = "text/plain,charset=UTF-8";
// try to get response from HTTP/HTTPS
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream ss = response.GetResponseStream();
using (StreamReader reader = new StreamReader(ss, Encoding.UTF8))
{
string data = reader.ReadToEnd();
return data;
}
}
catch (Exception e)
{
string err = e.Message;
}
return "";
}
static public string ReadHTTPorHTTPSWithPost(string URL, string strPostdata)
{
try
{
System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
// combine HTTP request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
// HTTP request header
request.Method = "post";
request.ContentType = "text/plain,charset=UTF-8";
// HTTP request body
byte[] buffer = Encoding.UTF8.GetBytes(strPostdata);
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
// try to get response from HTTP/HTTPS
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream ss = response.GetResponseStream();
using (StreamReader reader = new StreamReader(ss, Encoding.UTF8))
{
string data = reader.ReadToEnd();
return data;
}
}
catch (Exception e)
{
string err = e.Message;
}
return "";
}
static void Main(string[] args)
{
string ret = ReadHTTPorHTTPSWithGet("https://www.baidu.com");
string ret2 = ReadHTTPorHTTPSWithGet("http://www.baidu.com");
if (ret.Equals(ret2))
{
System.Console.WriteLine("success, results for http and https are same.");
}
else
{
System.Console.WriteLine("failed");
}
}
}
}