共用方式為


安全通道

安全通道(安全通道)安全性套件,其驗證服務標識符為RPC_C_AUTHN_GSS_SCHANNEL,支援下列公鑰型通訊協定:SSL (安全套接字層) 2.0 和 3.0 版、傳輸層安全性 (TLS) 1.0 和私人通訊技術 (PCT) 1.0 版。 TLS 1.0 是 1999 年 1 月由因特網工程工作組 (IETF) 在 RFC 2246 檔中 發行的標準化、稍微修改的 SSL 3.0 版本。 由於 TLS 已標準化,因此鼓勵開發人員使用 TLS 而非 SSL。 PCT 僅包含回溯相容性,不應用於新的開發。 使用安全通道安全性套件時,DCOM 會根據用戶端和伺服器功能自動交涉最佳的通訊協定。

下列主題簡短描述 TLS 通訊協定及其如何與 DCOM 搭配運作。

注意

這些章節中 TLS 通訊協定的所有資訊也適用於 SSL 和 PCT 通訊協定。

 

使用 TLS 的時機

當伺服器需要向匿名客戶端證明其身分識別時,TLS 是唯一可用的安全性選項。 對於想要參與電子商務的網站來說,這特別重要,因為它有助於保護敏感性資訊的傳輸,例如信用卡號碼。 TLS 可確保電子商務客戶可以確定他們正在做生意的人,因為他們得到了伺服器身分識別的證明。 它也為電子商務伺服器提供效率,因為不需要擔心驗證其每個客戶的身分識別。

TLS 要求所有伺服器向客戶端證明其身分識別。 此外,TLS 提供讓用戶端向伺服器證明其身分識別的選項。 這項相互驗證對於限制大型公司內部網路中特定網頁的存取很有用。

TLS 支援最強的驗證層級,並提供開放架構,允許加密強度隨著時間增加,以跟上技術創新。 TLS 是傳輸中數據所需的最高安全性環境的最佳選擇。

TLS 運作方式的簡短概觀

TLS 是以公鑰基礎結構 (PKI) 為基礎,其使用公開/私鑰組來啟用數據加密和建立數據完整性,並使用 X.509 憑證進行驗證。

許多安全性通訊協定,例如 Kerberos v5 通訊協定,相依於單一密鑰來加密和解密數據。 因此,這類通訊協定取決於加密密鑰的安全交換;在 Kerberos 通訊協定中,這是透過從密鑰發佈中心 (KDC) 取得的票證來完成。 這要求使用 Kerberos 通訊協定的每個人都向 KDC 註冊,這對電子商務網頁伺服器來說是一項不切實際的限制,旨在吸引來自世界各地的數百萬客戶。 因此,TLS 依賴 PKI,它會使用兩個金鑰進行數據加密。「當配對的一個金鑰加密數據時,只有配對的另一個密鑰可以解密它。 此設計的主要優點是可以執行加密,而不需要安全交換加密密鑰。

PKI 會使用一種技術,其中其中一個密鑰會保持私用,而且只能供註冊的主體使用,而另一個密鑰則公開供任何人存取。 如果有人想要將私人訊息傳送給密鑰組的擁有者,訊息可以使用公鑰加密,而且只能使用私鑰來解密訊息。

金鑰組也可用來驗證所傳送數據的完整性。 若要這樣做,金鑰組的擁有者可以在傳送數字簽名之前,先將數位簽名附加至數據。 建立數位簽名牽涉到計算數據的哈希,並使用私鑰加密哈希。 任何使用公鑰來解密數位簽名的人都確信數位簽名必須只來自擁有私鑰的人員。 此外,收件者可以使用與寄件者相同的演算法來計算數據的哈希,如果導出的哈希符合數位簽名中傳送的哈希,收件者可以確定數據在數位簽署之後未修改。

使用 PKI 進行大量數據加密的其中一個缺點是其效能相對較慢。 由於涉及密集的數學,使用相依於密鑰組的非對稱式加密來加密和解密數據的速度可能會比加密和解密慢 1,000 倍,而使用只相依於單一密鑰的對稱加密和解密。 因此,TLS 只會使用 PKI 來產生數位簽名,以及交涉用戶端和伺服器將用於大量數據加密和解密的會話特定單一密鑰。 TLS 支援各種不同的單一密鑰對稱密碼,未來可能會新增額外的加密。

如需 TLS 交握通訊協定的詳細資訊,請參閱 TLS 交握通訊協定

如需 TLS 通訊協定後置密碼編譯的詳細資訊,請參閱 密碼編譯基本概念

X.509 憑證

PKI 必須處理的重要問題是能夠信任所使用公鑰的真實性。 當您使用發行給想要做生意的公司公鑰時,您想要確定密鑰實際上屬於公司,而不是想要探索信用卡號碼的竊賊。

為了保證主體持有金鑰組的身分識別,主體是由證書頒發機構單位 (CA) 簽發 X.509 憑證。 此憑證包含識別主體的資訊、包含主體的公鑰,並由 CA 以數位方式簽署。 此數字簽名表示 CA 認為憑證中包含的公鑰確實屬於憑證所識別的主體。

您如何信任 CA? 因為 CA 本身會保存由較高層級 CA 簽署的 X.509 憑證。 此憑證簽章鏈會繼續執行,直到到達根 CA,也就是簽署自己憑證的 CA。 如果您信任憑證根 CA 的完整性,您應該能夠信任憑證本身的真實性。 因此,挑選您願意信任的根 CA 是系統管理員的重要職責。

用戶端憑證

當安全性傳輸層通訊協定首次出現時,其主要目的是保證用戶端連線到正宗伺服器,並協助保護傳輸中數據的隱私權。 不過,SSL 3.0 和 TLS 1.0 也包含支持在通訊協定交握期間傳輸客戶端憑證。 此選擇性功能可啟用客戶端和伺服器的相互驗證。

是否應該在應用程式內容中決定要使用用戶端憑證。 如果主要需求是驗證伺服器,則不需要客戶端憑證。 不過,如果客戶端驗證很重要,則可以使用用戶端的憑證,而不是依賴應用程式內的自定義驗證。 使用用戶端憑證比自定義驗證更理想,因為它為使用者提供單一登錄案例。

在 COM 中使用 TLS

TLS 僅支持模擬層級 (RPC_C_IMP_LEVEL_IMPERSONATE) 模擬。 如果 COM 交涉 TLS 做為 Proxy 上的驗證服務,COM 將會將模擬層級設定為模擬,而不論進程預設值為何。 若要讓模擬在 TLS 中正常運作,客戶端必須將 X.509 憑證提供給伺服器,而且伺服器必須有該憑證對應至伺服器上的特定用戶帳戶。 如需詳細資訊,請參閱 將憑證對應至用戶帳戶的逐步指南。

TLS 不支援 蓋。 如果在 CoInitializeSecurity 或 IClientSecurity::SetBlanket 呼叫中指定了遮蓋旗標和 TLS,則會傳回E_INVALIDARG。

TLS 不適用於設定為 None 的驗證層級。 用戶端與伺服器之間的交握會檢查每個 設定的驗證層級,並針對連線選擇較高的安全性設定。

您可以呼叫 CoInitializeSecurity CoSetProxyBlanket 來設定 TLS 的安全性參數。 下列各節說明進行這些呼叫所涉及的細微差別。

伺服器如何設定安全性毛毯

如果伺服器想要使用 TLS,則必須在 CoInitializeSecurity asAuthSvc 參數中,將安全通道 (RPC_C_AUTHN_GSS_SCHANNEL) 指定為驗證服務。 若要防止用戶端使用較不安全的驗證服務連線到伺服器,伺服器在呼叫 CoInitializeSecurity 時,應該只將 Schannel 指定為驗證服務。 伺服器在呼叫 CoInitializeSecurity 之後,就無法變更安全性毛毯。

若要使用 TLS,當伺服器呼叫 CoInitializeSecurity 時,應該指定下列參數:

  • pVoid 應該是 IAccessControl 物件的指標或SECURITY_DESCRIPTOR指標。 它不應該是 NULL 或 AppID 的指標。
  • cAuthSvc 不能是 0 或 -1。 當 cAuthSvc為 -1 時,COM 伺服器永遠不會選擇 Schannel。
  • asAuthSvc 必須將 Schannel 指定為可能的驗證服務。 這是藉由為SOLE_AUTHENTICATION_LIST的 Schannel 成員設定下列SOLE_AUTHENTICATION_SERVICE參數來完成:
    • dwAuthnSvc 必須RPC_C_AUTHN_GSS_SCHANNEL。
    • dwAuthzSvc 應RPC_C_AUTHZ_NONE。 目前會忽略它。
    • pPrincipalName 必須是CERT_CONTEXT的指標,轉換為 OLECHAR 的指標,代表伺服器的 X.509 憑證。
  • dwAuthnLevel 表示從用戶端接受成功連線的最低驗證層級。 它不能RPC_C_AUTHN_LEVEL_NONE。
  • dwCapabilities 不應該設定EOAC_APPID旗標。 如果 pVoid 指向 IAccessControl 物件,則應該設定EOAC_ACCESS_CONTROL旗標;如果 pVoid 指向SECURITY_DESCRIPTOR,則不應該設定旗標。 如需可能設定的其他旗標,請參閱 CoInitializeSecurity

如需使用 CoInitializeSecurity 的詳細資訊,請參閱使用 CoInitializeSecurity 設定全行程安全性。

用戶端如何設定安全性毛毯

如果用戶端想要使用 TLS,則必須在 CoInitializeSecurity 的 pAuthList 參數的驗證服務清單中指定安全通道 (RPC_C_AUTHN_GSS_SCHANNEL)。 如果在呼叫 CoInitializeSecurity 時未將 Schannel 指定為可能的驗證服務,則如果稍後呼叫 CoSetProxyBlanket (或 IClientSecurity::SetBlanket)嘗試將 Schannel 指定為驗證服務,將會失敗。

當用戶端呼叫 CoInitializeSecurity 時,應該指定下列參數:

  • dwAuthnLevel 會指定用戶端想要使用的預設驗證層級。 它不能RPC_C_AUTHN_LEVEL_NONE。
  • dwImpLevel 必須RPC_C_IMP_LEVEL_IMPERSONATE。
  • pAuthList 必須具有下列 SOLE_AUTHENTICATION_INFO 參數做為列表的成員:
    • dwAuthnSvc 必須RPC_C_AUTHN_GSS_SCHANNEL。
    • dwAuthzSvc 必須RPC_C_AUTHZ_NONE。
    • pAuthInfo 是CERT_CONTEXT指標,轉換成 void 的指標,代表用戶端的 X.509 憑證。 如果客戶端沒有憑證或不想將其憑證呈現給伺服器, pAuthInfo 必須是 NULL ,而且會嘗試與伺服器進行匿名連線。
  • dwCapabilities 是一組旗標,表示其他用戶端功能。 如需應該設定哪些旗標的相關信息,請參閱 CoInitializeSecurity

如需使用 CoInitializeSecurity 的詳細資訊,請參閱使用 CoInitializeSecurity 設定全行程安全性。

用戶端如何變更安全性毛毯

如果用戶端想要使用 TLS,但在呼叫 CoInitializeSecurity 之後變更安全性毛毯,它必須呼叫 CoSetProxyBlanket 或 IClientSecurity::SetBlanket,其參數與呼叫 CoInitializeSecurity 中所使用的參數類似,但有下列差異:

  • pServerPrincName 會以 msstd 或 fullsic 格式指出伺服器的主體名稱。 如需這些格式的資訊,請參閱 主體名稱。 如果用戶端有伺服器的 X.509 憑證,則可以呼叫 RpcCertGeneratePrincipalName 來尋找主體名稱。
  • pAuthInfo 是CERT_CONTEXT指標,轉換成RPC_AUTH_IDENTITY_HANDLE的指標,代表用戶端的 X.509 憑證。 如果客戶端沒有憑證或不想將其憑證呈現給伺服器, pAuthInfo 必須是 NULL ,而且會嘗試與伺服器進行匿名連線。
  • dwCapabilities 是由指出其他用戶端功能的旗標所組成。 只有四個旗標可用來變更安全性毛毯設定:EOAC_DEFAULT、EOAC_MUTUAL_AUTH、EOAC_ANY_AUTHORITY(此旗標已被取代),以及EOAC_MAKE_FULLSIC。 如需詳細資訊,請參閱 CoSetProxyBlanket

如需使用 CoSetProxyBlanket 的詳細資訊,請參閱在介面 Proxy 層級設定安全性。

範例:客戶端變更安全性毛毯

下列範例示範用戶端如何變更安全性毛毯,以配合來自伺服器的要求,讓用戶端提供其 X.509 憑證。 為了簡潔起見,會省略錯誤處理程序代碼。

void ClientChangesSecurity ()
{
  HCRYPTPROV                   provider           = 0;
  HCERTSTORE                   cert_store         = NULL;
  PCCERT_CONTEXT               client_cert        = NULL;
  PCCERT_CONTEXT               server_cert        = NULL;
  WCHAR                        *server_princ_name = NULL;
  ISecret                      *pSecret           = NULL;
  MULTI_QI                     server_instance;
  COSERVERINFO                 server_machine;
  SOLE_AUTHENTICATION_LIST     auth_list;
  SOLE_AUTHENTICATION_INFO     auth_info[1];



  // Specify all the authentication info. 
  // The client is willing to connect using SChannel,
  //   with no client certificate.
  auth_list.cAuthInfo     = 1;
  auth_list.aAuthInfo     = auth_info;
  auth_info[0].dwAuthnSvc = RPC_C_AUTHN_GSS_SCHANNEL;
  auth_info[0].dwAuthzSvc = RPC_C_AUTHZ_NONE;
  auth_info[0].pAuthInfo  = NULL;  // No certificate

  // Initialize client security with no client certificate.
  CoInitializeSecurity( NULL, -1, NULL, NULL,
                        RPC_C_AUTHN_LEVEL_PKT,
                        RPC_C_IMP_LEVEL_IMPERSONATE, &auth_list,
                        EOAC_NONE, NULL );
  
  // Specify info for the proxy.
  server_instance = {&IID_ISecret, NULL, S_OK};
  server_machine  = {0, L"ServerMachineName", NULL, 0};
  
  // Create a proxy.
  CoCreateInstanceEx( CLSID_Secret, NULL, CLSCTX_REMOTE_SERVER, 
                      &server_machine, 1, &server_instance);
  pSecret = (ISecret *) server_instance.pItf;

  //** The client obtained the server's certificate during the handshake.
  //** The server requests a certificate from the client.

  // Get the default certificate provider.
  CryptAcquireContext( &provider, NULL, NULL, PROV_RSA_SCHANNEL, 0 );

  // Open the certificate store.
  cert_store = CertOpenSystemStore( provider, L"my" );

  // Find the client's certificate.
  client_cert = 
    CertFindCertificateInStore( cert_store,
                                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                0,
                                CERT_FIND_SUBJECT_STR,
                                L"ClientName",  // Use the principal name
                                NULL );

  // Find the fullsic principal name of the server.
  RpcCertGeneratePrincipalName( server_cert, RPC_C_FULL_CERT_CHAIN, 
                                &server_princ_name );

  // Change the client's security: 
  // Increase the authentication level and attach a certificate.
  CoSetProxyBlanket( pSecret, RPC_C_AUTHN_GSS_SCHANNEL,
                     RPC_C_AUTHZ_NONE,
                     server_princ_name, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                     RPC_C_IMP_LEVEL_IMPERSONATE, 
                     (RPC_AUTH_IDENTITY_HANDLE *) client_cert,
                     EOAC_NONE );

  cleanup:
  if (server_princ_name != NULL)
    RpcStringFree( &server_princ_name );
  if (client_cert != NULL)
    CertFreeCertificateContext(client_cert);
  if (server_cert != NULL)
    CertFreeCertificateContext(server_cert);
  if (cert_store != NULL)
    CertCloseStore( cert_store, CERT_CLOSE_STORE_CHECK_FLAG );
  if (provider != 0 )
    CryptReleaseContext( provider, 0 );
  if (pSecret != NULL)
    pSecret->Release();
  CoUninitialize();
}

COM 和安全性套件