2011-09-07 153 views
19

我想使用C++在Win32中驗證SSL證書。我想我想使用Cert * API,這樣我就可以獲得Windows證書存儲的好處。這是我想出來的。在Win32中驗證SSL證書的正確方法是什麼?

  • 它是正確的嗎?
  • 有沒有更好的方法來做到這一點?
  • 我做錯了什麼?
bool IsValidSSLCertificate(PCCERT_CONTEXT certificate, LPWSTR serverName) 
{ 
    LPTSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH }; 

    CERT_CHAIN_PARA params       = { sizeof(params) }; 
    params.RequestedUsage.dwType      = USAGE_MATCH_TYPE_AND; 
    params.RequestedUsage.Usage.cUsageIdentifier  = _countof(usages); 
    params.RequestedUsage.Usage.rgpszUsageIdentifier = usages; 

    PCCERT_CHAIN_CONTEXT chainContext = 0; 

    if (!CertGetCertificateChain(NULL, 
            certificate, 
            NULL, 
            NULL, 
            &params, 
            CERT_CHAIN_REVOCATION_CHECK_CHAIN, 
            NULL, 
            &chainContext)) 
    { 
     return false; 
    } 

    SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslPolicy = { sizeof(sslPolicy) }; 
    sslPolicy.dwAuthType      = AUTHTYPE_SERVER; 
    sslPolicy.pwszServerName     = serverName; 

    CERT_CHAIN_POLICY_PARA policy = { sizeof(policy) }; 
    policy.pvExtraPolicyPara  = &sslPolicy; 

    CERT_CHAIN_POLICY_STATUS status = { sizeof(status) }; 

    BOOL verified = CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, 
                 chainContext, 
                 &policy, 
                 &status); 

    CertFreeCertificateChain(chainContext); 
    return verified && status.dwError == 0; 
} 
+0

你沒有提到你使用的是什麼,但是,如果你在典型的HTTPS場景中使用它,通常應該傳遞CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。 – EricLaw

+0

我主要想用它來驗證LDAP服務器SSL證書(比如,在VERIFYSERVERCERT函數中)。我也想用它來驗證客戶端/服務器應用程序中的HTTPS服務器證書,客戶端可以爲服務器指定自己的SSL證書。 – briangreenery

+0

使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT而不是CERT_CHAIN_REVOCATION_CHECK_CHAIN更常見嗎?你爲什麼不檢查根證書的撤銷? – briangreenery

回答

1

我認爲最好的答案取決於正是你正在嘗試做的。

我會告誡你,SSL是基於兩個終端都需要安全連接的假設。如果任一端點對維護安全性不感興趣,則不存在。

將字節代碼放入分佈式代碼中只需返回true即可。這就是爲什麼Windows將大量驗證移入內核的原因。但他們並沒有預料到人們在虛擬硬件上運行Windows,這使得繞過操作系統變得微不足道。

現在考慮您希望從某個來源獲得認證,但假設該來源無法從可靠來源提供相同的信息。然後把它交給你。所以你不能依靠證書來「證明」任何人是特別的人。

從證書中獲得的唯一保護是防止外部人員而不是端點破壞被傳輸郵件的機密性。

任何其他用途註定會失敗,並最終會失敗並帶來潛在的災難性後果。

對不起,大帖子。評論部分有一個字數限制。

+2

感謝您的回答,儘管信息中包含的信息是有效的,但我更希望有人能夠驗證上述代碼在使用Windows加密API驗證SSL使用證書的上下文中是否正確。我很清楚SSL在這些方面的侷限性,但在這裏我只關心讓我的端點絕對正確。對於上下文,我一直在爲PHP核心開發[此補丁](https://github.com/php/php-src/pull/601) - 所以我們並不擔心特定的應用程序,只有這個我們提供的工具是正確的。 – DaveRandom

14

您應該知道RFC3280 section 6.1RFC5280 section 6.1。兩者都描述了驗證證書路徑的算法。儘管Win32 API爲您處理了一些事情,但通常瞭解該過程仍然很有價值。

此外,這裏是一個(在我看來)非常值得信賴的參考:Chromium certificate verification code。總體而言,我認爲你的代碼並不正確。但這裏有幾件事情我想看看/改變,如果我是你:

1.獨立的通用名稱驗證

鉻從環比分別驗證證書的通用名稱。顯然他們已經注意到它的一些問題。見他們的理由的評論:

cert_verify_proc.win.cc:731 // Certificate name validation happens separately, later, using an internal 
cert_verify_proc.win.cc:732 // routine that has better support for RFC 6125 name matching. 

2.使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT

鉻也使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT標誌,而不是CERT_CHAIN_REVOCATION_CHECK_CHAIN的。在我找到他們的代碼之前,我實際上已經開始考慮這一點,並且強化了我的信念,即您應該使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT。

儘管前面提到的RFC都指定自簽名信任錨不被視爲鏈的一部分,但CertGetCertificateChain(http://msdn.microsoft.com/en-us/library/windows/desktop/aa376078(v=vs.85).aspx)的文檔聲明,它構建了一個鏈,直到可能的話,還有受信任的根證書。受信任的根證書與可信自簽名證書一起定義(在同一頁上)。

這消除了* EXCLUDE_ROOT可能跳過對非根信任錨點的撤銷檢查(即使它不是任何RFC的要求,但實際上需要信任錨點是自簽名的)。記錄)。

現在,由於根CA證書無法撤銷自己(CRL無法簽名/驗證),因此在我看來,這兩個標誌是相同的。

我做了一些Google搜索,偶然發現了這個論壇帖子:http://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/9f95882a-1a68-477a-80ee-0a7e3c7ae5cf/x509revocationflag-question?forum=windowssecurity。 .NET產品組的成員(據推測)聲稱,如果根是自簽名的,實際上的標誌行爲是相同的(理論上,如果ENTIRE_CHAIN標誌包含CDP擴展名,它將檢查根證書的撤銷,但是不能發生)。

他還建議使用* EXCLUDE_ROOT標誌,因爲如果自簽名根CA包含CDP擴展名,則其他標誌可能會導致不必要的網絡請求。

不幸的是:

  • 我無法找到這兩個標誌之間的差異的任何正式文件解釋。
  • 儘管鏈接的討論可能適用於.NET引擎下的相同Win32 API標誌,但不能保證。

完全確信它的確定使用CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,我用Google搜索了一點,發現鉻SSL證書驗證碼,我掛在我的答覆的頂部。

作爲一個額外的獎勵,鉻cert_verify_proc_win.cc文件包含有關IE的驗證碼以下提示:

618: // IE passes a non-NULL pTime argument that specifies the current system 
619: // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the 
620: // chain_flags argument. 

不知道他們是如何知道這一點,但在這一點上,我會使用感覺很舒服CERT_CHAIN_REVOCATION_CHECK_EXCLUDE_ROOT。

3.不同接受證書用法

我注意到鉻還規定3層證書的用途,而不是1:

szOID_PKIX_KP_SERVER_AUTH, 
szOID_SERVER_GATED_CRYPTO, 
szOID_SGC_NETSCAPE 

從我可以通過谷歌收集的其他用途可以由舊版本的網絡要求瀏覽器,否則他們可能無法建立安全連接。

如果Chromium認爲包含這些用法,我會效仿。

請注意,如果更改代碼,則還應該將params.RequestedUsage.dwType設置爲USAGE_MATCH_TYPE_OR而不是USAGE_MATCH_TYPE_AND。

-

我想不出任何其他意見。但如果我是你,我會自己檢查Chromium源(也可能是Firefox) - 只是爲了確保我沒有遺漏任何東西。

-1

功能CertGetCertificateChainCertVerifyCertificatePolicy走在一起。這部分是正確的。

  • CERT_CHAIN_REVOCATION_CHECK_END_CERT
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN
  • CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT:

    對於CertGetCertificateChain的標誌可以,如果你想檢查撤銷設置爲任何有以下三種。

只有其中一個可以使用,這三個選項不能是ORed。除了這些旗幟之一,您可以考慮如何創建鏈條;使用local cache或者僅使用CRLOCSP。對於這些考慮read this link

執行函數出錯或更簡單,如果返回值爲0,這並不意味着證書無效,而是您無法執行該操作。有關錯誤信息,請使用GetLastError()。所以你的返回false的邏輯是錯誤的,它更多的是拋出錯誤並讓客戶端代碼決定是重試還是繼續做其他事情。

this link有一個部分叫「分類錯誤」,請閱讀。基本上你應該檢查certChainContext->TrustStatus.dwErrorStatus. Here a list of error statuses will be ORed. Please check CERT_TRUST_STATUS msdn參考。所以在這裏你可以有你的商業邏輯。例如,如果您發現該值(CERT_TRUST_REVOCATION_STATUS_UNKNOWN | CERT_TRUST_IS_OFFLINE_REVOCATION)的錯誤狀態證書吊銷檢查無法執行,您可以選擇決定您想要的內容(讓證書繼續或將其標記爲無效)。

因此,在打電話給CertVerifyCertificatePolicy之前,您可以選擇丟棄或已經標記驗證錯誤。

如果您選擇來CertVerifyCertificatePolicy,鉻代碼是關於如何將policy_status.dwError映射到您的錯誤類/枚舉的絕妙參考。