WebAPi之SelfHost自建立證書啟動Https疑難解惑及無法正確返回結果 WebAPi之SelfHost自建立證書啟動Https疑難解惑及無法正確返回結果
WebAPi之SelfHost自建立證書啟動Https疑難解惑及無法正確返回結果
前言
話說又來需求了,之前對於在SelfHost中需要巢狀頁面並操作為非正常需求,這回來正常需求了,客戶端現在加了https,老大過來說WebAPi訪問不了了,這是什麼情況,我去試了試,還真是這個情況,不知道如何下手啊,最終為了解決這個問題,漫長的探索之旅就這樣開始了,希望給需要在SelfHost下啟動Https的童鞋一點啟示和幫助。
話題引入
當在客戶端傳送請求傳送不過去時看了看谷歌控制檯顯示的訊息大意是不能混合使用即客戶端為https,則WebAPi不能為http,估計是為了安全的問題,於是乎問題就比較明朗了,只需要將WebAPi修改成https即可,直接將SelfHost中地址修改為Https顯然是不行的必須要藉助證書並開啟對應的端口才行,既然我們已經分析完畢,接下來我們解決WebAPi啟動Https的問題即可。開始想到去申請的一個免費的證書就行,但是由於是將WebAPi安裝在本地Windows服務中且ip不固定,所以免費的證書則不再可取,只能自行建立證書來解決這個問題。但是我對於證書幾乎從未接觸過,一無所知,不知從哪裡下手,這一漫長的過程就從此開始。
解決SelfHost啟動Https
在園中搜索有關自建立證書的答案,基本要麼是IIS,要麼是WCF建立證書,不過還是有了一點思路,提供了自建立證書需要的命令,開始有一點思緒。當看到如下一條命令時我要驚呆了,難道就這樣解決了嗎,so easy!
第一次嘗試
http add urlacl url=https://+:port/ user=""
結果發現這只是url的保留項而已。
第二次嘗試
我們通過VS開發命令來建立證書
第一步:
MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r
【注意】:該命令最後一個-r不能缺少,-r是建立自建立證書的標識,否則當利用Pvk2pfx建立私鑰時則會出現【ERROR: File not found.(Error Code = 0x80070002)】
結果變成如下:
第二步:
開啟MMC將建立的證書匯入到受信任的頒發證書機構中,則變成如下這樣:
第三步:
拿到該證書的指紋並監聽ip以及埠,通過如下命令進行:
http add sslcert ipport=0.0.0.0:8084 certhash=996645BAB7169F2AFD7599A696DA2586862843C6 appid={41992502-E5D4-4794-BB01-D4A7414480CC}
一切都是如此的完美,最後看下結果,再次讓我大失所望:
此時我已經處在崩潰的邊緣,搜尋這個原因的答案,都無法解決,此時只能求助萬能的stackoverflow,看到這個答案令人驚喜一番:
http://stackoverflow.com/questions/13076915/ssl-certificate-add-failed-when-binding-to-port
大意是將自建立的證書要通過MMC首先匯入到【個人】證書中才行,於是乎將其拖到個人證書中看看。
結果依然是這個錯誤,當回過來再看上述所有回答答案時,下面一個回答簡直是拯救了我,這個問題困擾我一天,讓我無比激動。請看這句話:
You can easily check if your certificate has private a key as so:
mmc
-certificates
-local machine
-personal
. Look at the icon of the certificate - it MUST have key sign on the icon.
還要在本地計算中的個人證書中看自建立的證書左上角是否有個小鑰匙,這個鑰匙也就是私鑰,我們還得建立私鑰才行,通過如下命令進行。
第四步:
建立證書的私鑰
Pvk2pfx -pvk DevelopmentCA.pvk -spc DevelopmentCA.cer -pfx DevelopmentSSL.pfx -po password
接下來我們再來執行第三步則成功如下:
我們接下來執行程式https:localhost:8084來驗收成果,結果如下:
那麼這個問題又該如何解決呢?可能有些人就得說了,點選下面的高階直接前往不就可以了嗎,雖然這樣式可以接受,但是在我們專案中,是通過【裝置】去訪問WebAPi,基於這點絕對不可行,so must kill it。當我各種嘗試後發現這樣做卻可以。我們將 MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r 中的CN修改為localhost即頒發給localhost時卻可以,重複性的動作則不再演示,給演示最終結果如下:
注意:當到這裡時如果你還是發現證書是無效時請進行如下操作,參考資料來源於搜尋時來自於youtube的一段視訊演示,下面請看:
將演示最後中【允許將識別符號用於受保護內容(可能需要重新重啟計算機)】去掉即可。
到了這裡關於在WebAPi之SelfHost啟動Https的問題基本上解決了一大半,對於我來說,對於你來說可能已經Over,但是在實際場景中卻還沒完成。當在測試時發現如上壓根不會請求到WebAPi,此時在谷歌控制檯卻出現如下的錯誤:
::Net_Error_Response
第三次嘗試
解決客戶端無法訪問localhost。
百思不得其解,搜尋資料時以為是跨域的問題,結果卻不是,在客戶端那邊是用的WebAPi的本地IP來訪問,所以想想是不是localhost不行呢。本想偷點懶,在Hosts資料夾裡對localhost進行對映,結果依然不行,於是乎訪問地址變成:https:192.168.3.6:8084最終完事。
到了這裡也就完成了90%,個人還不是很滿足,尋思著雖然有一點是繞不過去,那就是首先得將證書匯入【受信任的頒發機構中】,但是還需要通過MMC將證書匯入個人證書,這一點是我無法接受,我繼續開始探索之旅。通過程式碼的形式將證書匯入到MMC的【個人】證書中。
第四次嘗試
通過程式碼形式將證書匯入到【個人】證書中,而非通過MMC匯入尋求解決方案。
通過執行如下程式碼來嘗試建立訪問https證書:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName, "password"); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close(); Process p = new Process(); p.StartInfo.FileName = "netsh.exe"; p.StartInfo.Arguments = string.Format("http add sslcert ipport=0.0.0.0:8084 certhash={0} appid={1}", cert.GetCertHashString(), "{" + "41992502-E5D4-4794-BB01-D4A7414480CC"+"}"); p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.Start(); p.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時通過如下命令來檢視https是否已經開啟:
netsh http show sslcert
結果未看到顯示開啟的埠,此時想或許是否是許可權不夠呢?於是將啟動程序的身份執行以【管理員】身份執行,在上述啟動程序中新增如下程式碼:
p.StartInfo.Verb = "runas";
此時並未有什麼影響,需要開啟的埠依然巋然不動,將WebAPi寄宿在Windows服務中,我們是利用批處理來進行,於是嘗試利用批處理部署一下來看看,看下演示結果:
利用批處理則圓滿完成任務並滿足要求,不知道為何直接並用管理員身份執行不可,或許是許可權還是不夠吧,至少還是找到了一種可行的解決方案!
無法正確返回結果
當一切準備就緒時,前端又反應返回的結果不正確不能解析,一波未平一波又起,繼續fighting,在控制檯如下顯示:
這個結果聞所未聞,居然出現一個【 k__BackingField 】 欄位,經過查詢相關資料得出:在WebAPi中預設是利用JSON.Net中的【 DefaultContractResolver 】來解析物件,當對解析物件上新增【 Serializable 】物件時則會造成上述原因,例如:
[Serializable] public class Person { public int Age { get; set; } }
此時應將【Serializable】特性去除或者在全域性配置新增如下語句才能正確顯示需要的結果,來忽略預設特性:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver { IgnoreSerializableAttribute = true };
【建議】:無論是有無新增上述序列化特性,建議都在全域性配置新增上述忽略預設序列化特性。
參考資料:
http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield
這一切是不是就這樣完了呢?任務算是完成了,為了對證書有更加深入的理解,我們來拓展一下證書知識。
證書知識擴充套件
兩個伺服器之間是如何利用證書來進行相互之間的信任呢?請看下圖:
在如上圖中,管理員通過交換雙方之間的公鑰中的指紋來建立二者伺服器之間的信任關係。對於證書上述我們也已經演示在.NET中通過【 X509Certificate2 】來實現,下面我們來看看這個類。
第一點:證書和PKCS #12/PFX檔案的不同
X509Certificate2此類有兩個屬性即Public Key(公鑰)和Private Key(私鑰),當我們匯入證書時可以是否匯出該私鑰,在Windows中典型的證書是以副檔名.cer結尾,當然它沒有包含私鑰。
下面我們可以這樣匯出一個證書:
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentCA.cer"; var cert = new X509Certificate2(fileName); File.WriteAllBytes(@"d:Hello.cer", cert.Export(X509ContentType.Cert));
有時我們需要匯出私鑰,此時私鑰及副檔名.Pfx結尾在另外一個檔案,可以通過如下匯出:
File.WriteAllBytes("Hello.pfx", cert.Export(X509ContentType.Pkcs12, (string)null));
Hello.pfx實際上就是一個PKCS#12檔案,它可以作為一個單獨檔案來儲存需要加密物件,做普遍的用途當然也就是用X509Certificate2來儲存私鑰,有關更多知識請參考:
https://en.wikipedia.org/wiki/PKCS_12
第二點:證書儲存
關於這點上述也已經演示,我們通過MMC開啟的是控制檯證書管理器,可以將當前使用者或本地計算機賬戶匯入其中,若只是想看當前使用者證書則可以通過certmgr.msc來開啟。那麼通過程式碼形式如何進行呢?如下:
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); store.Add(certificate); store.Close();
可以將證書匯入到通過StoreLocation.CurrentUser對映到當前使用者,通過StoreName.My對映到當前使用者個人證書中,也可以是本機計算機賬戶中的其他機構中通過上述列舉即可。此時則會新增如下注冊表中
HKEY_CURRENT_USER\SOFTWARE\Microsoft\SystemCertificates
或者通過桌面路徑
C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates
當然在本地計算中則是如下路徑:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates
第三點:理解私鑰
我們上述已經敘述過證書中是不帶私鑰,私鑰時單獨作為一個檔案來使用,那麼私鑰到底儲存在什麼地方呢?我們給出如下程式碼:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時私鑰會儲存在如下注冊表中:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\MY\Keys
如果我們進行如下修改將MachineKeySet修改為UserKeySet:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時則會儲存在如下地方:
C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Keys\
此時可能會出現一點問題,當將證書匯入並供本地計算機去使用,但是此時私鑰卻個人使用者資料夾中,要是計算機中的其他賬戶想要訪問這個私鑰可能沒有這個許可權或者說得不到這個私鑰。
關於列舉X509KeyStorageFlags的幾點說明:
(1)Exportable :建立證書時指定它可以用於備份私鑰。
(2)PersistKeySet:建立證書時指定它可以匯入一次並使用多次。
(3)UserKeySet:建立證書時指定它可以在另外一個賬戶使用它。
(4)MachineKeySet:建立證書時指定它可能導致其他賬戶沒有許可權或者訪問不到私鑰導致該私鑰不存在。
第四點:建立證書時謹慎
謹慎匯出證書利用位元組陣列 var certificate = new X509Certificate2(bytes); 此時會將檔案寫到臨時資料夾中,此時有可能臨時資料夾中有關此檔案不會得到有效的清理,導致臨時資料夾膨脹。
工具介紹
如下網址這裡可以看到通過MakeCert程式來建立證書已經被棄用(在PowerShell4.0之前我們可以下載MakeCert來自建立證書)。
https://msdn.microsoft.com/library/windows/desktop/aa386968.aspx
現在建立的證書可以通過PowerShell來進行(不過系統在Windows 8 或者Windows Server 2012或者Windows 8.1或者Windows Server 2012 R2),有關此命令的介紹詳情請見如下網址:
https://technet.microsoft.com/library/hh848633
看起來好像很難,實則比MakeCert命令更加簡潔明瞭,我們來看看(需要切換到PowerShell)。
(1)利用如下命令來建立證書並獲取到其指紋
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname localhost
頒發給localhost,並將其儲存到本地計算機中的【個人】證書下,結果得到如下:
(2)需要匯出證書時,需要用一個變數來儲存密碼
$pwd = ConvertTo-SecureString -String "Pa$$w0rd" -Force -AsPlainText
(3)匯出pfx,指定第一步獲取到的指紋和第二步儲存的密碼
Export-PfxCertificate -cert cert:\localMachine\my\CE0976529B02DE058C9CB2C0E64AD79DAFB18CF4 -FilePath d:cert.pfx -Password $pwd
總結
本節到這裡算是完全結束了,將在WebAPi使用過程中遇到的問題到一併敘述了一遍,其中整個做完花費了三天時間,寫這篇部落格花費了一天,很久沒有坐著花費接近一天的精力來寫一篇部落格,不過確確實實漲了不少知識,文中有關內容若有不妥之處,歡迎批評,同時也為了後續讓其他需要用到的童鞋少走點坑也是值得的,當然這裡在WebAPi中我們也需要認證客戶端是否已經採用ssl加密證書,通過繼承DelegatingHandler來進行處理,例如如下:
var uri = new UriBuilder(request.RequestUri); uri.Scheme = Uri.UriSchemeHttps; uri.Port = _httpsPort; //TO DO
在這裡非常感謝園友【幻天芒】,遇到難題解決不了或是沒什麼思路都在向他請教,感謝他的不厭其煩和指導,再次表示感謝。在這裡也提前預祝各位園友國慶快樂。
參考資料
http://southworks.com/blog/2014/06/16/enabling-ssl-client-certificates-in-asp-net-web-api/
http://stackoverflow.com/questions/28854466/makecert-exe-error
http://paulstovell.com/blog/x509certificate2
http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield
http://www.thewindowsclub.com/disable-insecure-content-warning-chrome
http://windowsitpro.com/blog/creating-self-signed-certificates-powershell
前言
話說又來需求了,之前對於在SelfHost中需要巢狀頁面並操作為非正常需求,這回來正常需求了,客戶端現在加了https,老大過來說WebAPi訪問不了了,這是什麼情況,我去試了試,還真是這個情況,不知道如何下手啊,最終為了解決這個問題,漫長的探索之旅就這樣開始了,希望給需要在SelfHost下啟動Https的童鞋一點啟示和幫助。
話題引入
當在客戶端傳送請求傳送不過去時看了看谷歌控制檯顯示的訊息大意是不能混合使用即客戶端為https,則WebAPi不能為http,估計是為了安全的問題,於是乎問題就比較明朗了,只需要將WebAPi修改成https即可,直接將SelfHost中地址修改為Https顯然是不行的必須要藉助證書並開啟對應的端口才行,既然我們已經分析完畢,接下來我們解決WebAPi啟動Https的問題即可。開始想到去申請的一個免費的證書就行,但是由於是將WebAPi安裝在本地Windows服務中且ip不固定,所以免費的證書則不再可取,只能自行建立證書來解決這個問題。但是我對於證書幾乎從未接觸過,一無所知,不知從哪裡下手,這一漫長的過程就從此開始。
解決SelfHost啟動Https
在園中搜索有關自建立證書的答案,基本要麼是IIS,要麼是WCF建立證書,不過還是有了一點思路,提供了自建立證書需要的命令,開始有一點思緒。當看到如下一條命令時我要驚呆了,難道就這樣解決了嗎,so easy!
第一次嘗試
http add urlacl url=https://+:port/ user=""
結果發現這只是url的保留項而已。
第二次嘗試
我們通過VS開發命令來建立證書
第一步:
MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r
【注意】:該命令最後一個-r不能缺少,-r是建立自建立證書的標識,否則當利用Pvk2pfx建立私鑰時則會出現【ERROR: File not found.(Error Code = 0x80070002)】
結果變成如下:
第二步:
開啟MMC將建立的證書匯入到受信任的頒發證書機構中,則變成如下這樣:
第三步:
拿到該證書的指紋並監聽ip以及埠,通過如下命令進行:
http add sslcert ipport=0.0.0.0:8084 certhash=996645BAB7169F2AFD7599A696DA2586862843C6 appid={41992502-E5D4-4794-BB01-D4A7414480CC}
一切都是如此的完美,最後看下結果,再次讓我大失所望:
此時我已經處在崩潰的邊緣,搜尋這個原因的答案,都無法解決,此時只能求助萬能的stackoverflow,看到這個答案令人驚喜一番:
http://stackoverflow.com/questions/13076915/ssl-certificate-add-failed-when-binding-to-port
大意是將自建立的證書要通過MMC首先匯入到【個人】證書中才行,於是乎將其拖到個人證書中看看。
結果依然是這個錯誤,當回過來再看上述所有回答答案時,下面一個回答簡直是拯救了我,這個問題困擾我一天,讓我無比激動。請看這句話:
You can easily check if your certificate has private a key as so:
mmc
-certificates
-local machine
-personal
. Look at the icon of the certificate - it MUST have key sign on the icon.
還要在本地計算中的個人證書中看自建立的證書左上角是否有個小鑰匙,這個鑰匙也就是私鑰,我們還得建立私鑰才行,通過如下命令進行。
第四步:
建立證書的私鑰
Pvk2pfx -pvk DevelopmentCA.pvk -spc DevelopmentCA.cer -pfx DevelopmentSSL.pfx -po password
接下來我們再來執行第三步則成功如下:
我們接下來執行程式https:localhost:8084來驗收成果,結果如下:
那麼這個問題又該如何解決呢?可能有些人就得說了,點選下面的高階直接前往不就可以了嗎,雖然這樣式可以接受,但是在我們專案中,是通過【裝置】去訪問WebAPi,基於這點絕對不可行,so must kill it。當我各種嘗試後發現這樣做卻可以。我們將 MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r 中的CN修改為localhost即頒發給localhost時卻可以,重複性的動作則不再演示,給演示最終結果如下:
注意:當到這裡時如果你還是發現證書是無效時請進行如下操作,參考資料來源於搜尋時來自於youtube的一段視訊演示,下面請看:
將演示最後中【允許將識別符號用於受保護內容(可能需要重新重啟計算機)】去掉即可。
到了這裡關於在WebAPi之SelfHost啟動Https的問題基本上解決了一大半,對於我來說,對於你來說可能已經Over,但是在實際場景中卻還沒完成。當在測試時發現如上壓根不會請求到WebAPi,此時在谷歌控制檯卻出現如下的錯誤:
::Net_Error_Response
第三次嘗試
解決客戶端無法訪問localhost。
百思不得其解,搜尋資料時以為是跨域的問題,結果卻不是,在客戶端那邊是用的WebAPi的本地IP來訪問,所以想想是不是localhost不行呢。本想偷點懶,在Hosts資料夾裡對localhost進行對映,結果依然不行,於是乎訪問地址變成:https:192.168.3.6:8084最終完事。
到了這裡也就完成了90%,個人還不是很滿足,尋思著雖然有一點是繞不過去,那就是首先得將證書匯入【受信任的頒發機構中】,但是還需要通過MMC將證書匯入個人證書,這一點是我無法接受,我繼續開始探索之旅。通過程式碼的形式將證書匯入到MMC的【個人】證書中。
第四次嘗試
通過程式碼形式將證書匯入到【個人】證書中,而非通過MMC匯入尋求解決方案。
通過執行如下程式碼來嘗試建立訪問https證書:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName, "password"); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close(); Process p = new Process(); p.StartInfo.FileName = "netsh.exe"; p.StartInfo.Arguments = string.Format("http add sslcert ipport=0.0.0.0:8084 certhash={0} appid={1}", cert.GetCertHashString(), "{" + "41992502-E5D4-4794-BB01-D4A7414480CC"+"}"); p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.Start(); p.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時通過如下命令來檢視https是否已經開啟:
netsh http show sslcert
結果未看到顯示開啟的埠,此時想或許是否是許可權不夠呢?於是將啟動程序的身份執行以【管理員】身份執行,在上述啟動程序中新增如下程式碼:
p.StartInfo.Verb = "runas";
此時並未有什麼影響,需要開啟的埠依然巋然不動,將WebAPi寄宿在Windows服務中,我們是利用批處理來進行,於是嘗試利用批處理部署一下來看看,看下演示結果:
利用批處理則圓滿完成任務並滿足要求,不知道為何直接並用管理員身份執行不可,或許是許可權還是不夠吧,至少還是找到了一種可行的解決方案!
無法正確返回結果
當一切準備就緒時,前端又反應返回的結果不正確不能解析,一波未平一波又起,繼續fighting,在控制檯如下顯示:
這個結果聞所未聞,居然出現一個【 k__BackingField 】 欄位,經過查詢相關資料得出:在WebAPi中預設是利用JSON.Net中的【 DefaultContractResolver 】來解析物件,當對解析物件上新增【 Serializable 】物件時則會造成上述原因,例如:
[Serializable] public class Person { public int Age { get; set; } }
此時應將【Serializable】特性去除或者在全域性配置新增如下語句才能正確顯示需要的結果,來忽略預設特性:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver { IgnoreSerializableAttribute = true };
【建議】:無論是有無新增上述序列化特性,建議都在全域性配置新增上述忽略預設序列化特性。
參考資料:
http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield
這一切是不是就這樣完了呢?任務算是完成了,為了對證書有更加深入的理解,我們來拓展一下證書知識。
證書知識擴充套件
兩個伺服器之間是如何利用證書來進行相互之間的信任呢?請看下圖:
在如上圖中,管理員通過交換雙方之間的公鑰中的指紋來建立二者伺服器之間的信任關係。對於證書上述我們也已經演示在.NET中通過【 X509Certificate2 】來實現,下面我們來看看這個類。
第一點:證書和PKCS #12/PFX檔案的不同
X509Certificate2此類有兩個屬性即Public Key(公鑰)和Private Key(私鑰),當我們匯入證書時可以是否匯出該私鑰,在Windows中典型的證書是以副檔名.cer結尾,當然它沒有包含私鑰。
下面我們可以這樣匯出一個證書:
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentCA.cer"; var cert = new X509Certificate2(fileName); File.WriteAllBytes(@"d:Hello.cer", cert.Export(X509ContentType.Cert));
有時我們需要匯出私鑰,此時私鑰及副檔名.Pfx結尾在另外一個檔案,可以通過如下匯出:
File.WriteAllBytes("Hello.pfx", cert.Export(X509ContentType.Pkcs12, (string)null));
Hello.pfx實際上就是一個PKCS#12檔案,它可以作為一個單獨檔案來儲存需要加密物件,做普遍的用途當然也就是用X509Certificate2來儲存私鑰,有關更多知識請參考:
https://en.wikipedia.org/wiki/PKCS_12
第二點:證書儲存
關於這點上述也已經演示,我們通過MMC開啟的是控制檯證書管理器,可以將當前使用者或本地計算機賬戶匯入其中,若只是想看當前使用者證書則可以通過certmgr.msc來開啟。那麼通過程式碼形式如何進行呢?如下:
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); store.Open(OpenFlags.ReadWrite); store.Add(certificate); store.Close();
可以將證書匯入到通過StoreLocation.CurrentUser對映到當前使用者,通過StoreName.My對映到當前使用者個人證書中,也可以是本機計算機賬戶中的其他機構中通過上述列舉即可。此時則會新增如下注冊表中
HKEY_CURRENT_USER\SOFTWARE\Microsoft\SystemCertificates
或者通過桌面路徑
C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates
當然在本地計算中則是如下路徑:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates
第三點:理解私鑰
我們上述已經敘述過證書中是不帶私鑰,私鑰時單獨作為一個檔案來使用,那麼私鑰到底儲存在什麼地方呢?我們給出如下程式碼:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時私鑰會儲存在如下注冊表中:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\MY\Keys
如果我們進行如下修改將MachineKeySet修改為UserKeySet:
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx"; var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); store.Add(cert); store.Close();
![複製程式碼](https://common.cnblogs.com/images/copycode.gif)
此時則會儲存在如下地方:
C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Keys\
此時可能會出現一點問題,當將證書匯入並供本地計算機去使用,但是此時私鑰卻個人使用者資料夾中,要是計算機中的其他賬戶想要訪問這個私鑰可能沒有這個許可權或者說得不到這個私鑰。
關於列舉X509KeyStorageFlags的幾點說明:
(1)Exportable :建立證書時指定它可以用於備份私鑰。
(2)PersistKeySet:建立證書時指定它可以匯入一次並使用多次。
(3)UserKeySet:建立證書時指定它可以在另外一個賬戶使用它。
(4)MachineKeySet:建立證書時指定它可能導致其他賬戶沒有許可權或者訪問不到私鑰導致該私鑰不存在。
第四點:建立證書時謹慎
謹慎匯出證書利用位元組陣列 var certificate = new X509Certificate2(bytes); 此時會將檔案寫到臨時資料夾中,此時有可能臨時資料夾中有關此檔案不會得到有效的清理,導致臨時資料夾膨脹。
工具介紹
如下網址這裡可以看到通過MakeCert程式來建立證書已經被棄用(在PowerShell4.0之前我們可以下載MakeCert來自建立證書)。
https://msdn.microsoft.com/library/windows/desktop/aa386968.aspx
現在建立的證書可以通過PowerShell來進行(不過系統在Windows 8 或者Windows Server 2012或者Windows 8.1或者Windows Server 2012 R2),有關此命令的介紹詳情請見如下網址:
https://technet.microsoft.com/library/hh848633
看起來好像很難,實則比MakeCert命令更加簡潔明瞭,我們來看看(需要切換到PowerShell)。
(1)利用如下命令來建立證書並獲取到其指紋
New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname localhost
頒發給localhost,並將其儲存到本地計算機中的【個人】證書下,結果得到如下:
(2)需要匯出證書時,需要用一個變數來儲存密碼
$pwd = ConvertTo-SecureString -String "Pa$$w0rd" -Force -AsPlainText
(3)匯出pfx,指定第一步獲取到的指紋和第二步儲存的密碼
Export-PfxCertificate -cert cert:\localMachine\my\CE0976529B02DE058C9CB2C0E64AD79DAFB18CF4 -FilePath d:cert.pfx -Password $pwd
總結
本節到這裡算是完全結束了,將在WebAPi使用過程中遇到的問題到一併敘述了一遍,其中整個做完花費了三天時間,寫這篇部落格花費了一天,很久沒有坐著花費接近一天的精力來寫一篇部落格,不過確確實實漲了不少知識,文中有關內容若有不妥之處,歡迎批評,同時也為了後續讓其他需要用到的童鞋少走點坑也是值得的,當然這裡在WebAPi中我們也需要認證客戶端是否已經採用ssl加密證書,通過繼承DelegatingHandler來進行處理,例如如下:
var uri = new UriBuilder(request.RequestUri); uri.Scheme = Uri.UriSchemeHttps; uri.Port = _httpsPort; //TO DO
在這裡非常感謝園友【幻天芒】,遇到難題解決不了或是沒什麼思路都在向他請教,感謝他的不厭其煩和指導,再次表示感謝。在這裡也提前預祝各位園友國慶快樂。
參考資料
http://southworks.com/blog/2014/06/16/enabling-ssl-client-certificates-in-asp-net-web-api/
http://stackoverflow.com/questions/28854466/makecert-exe-error
http://paulstovell.com/blog/x509certificate2
http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield
http://www.thewindowsclub.com/disable-insecure-content-warning-chrome
http://windowsitpro.com/blog/creating-self-signed-certificates-powershell