SChannel

安全通道 (Schannel) 安全包(其身份验证服务标识符为RPC_C_AUTHN_GSS_SCHANNEL)支持以下基于公钥的协议:SSL (安全套接字层) 版本 2.0 和 3.0、传输层安全性 (TLS) 1.0,以及专用通信技术 (PCT) 1.0。 TLS 1.0 是 1999 年 1 月由 Internet 工程工作组 (IETF) 颁发的 SSL 3.0 标准化、略有修改的版本,文档 RFC 2246。 由于 TLS 已标准化,因此鼓励开发人员使用 TLS 而不是 SSL。 仅包含用于向后兼容性的 PCT,不应用于新开发。 使用 Schannel 安全包时,DCOM 会根据客户端和服务器功能自动协商最佳协议。

以下主题简要介绍了 TLS 协议及其与 DCOM 的工作原理。

注意

这些部分中有关 TLS 协议的所有信息也适用于 SSL 和 PCT 协议。

 

何时使用 TLS

当服务器需要向匿名客户端证明其身份时,TLS 是唯一可用的安全选项。 对于想要参与电子商务的网站来说,这尤其重要,因为它有助于保护信用卡号等敏感信息的传输。 TLS 确保电子商务客户可以确定他们正在其开展业务,因为他们得到了服务器的标识证明。 它还使电子商务服务器效率不必担心对每个客户的标识进行身份验证。

TLS 要求所有服务器向客户端证明其标识。 此外,TLS 还提供让客户端向服务器证明其标识的选项。 此相互身份验证可用于限制大型企业 Intranet 中某些网页的访问。

TLS 支持最强的身份验证级别,并提供一个开放体系结构,允许加密强度随时间推移而增加,以跟上技术创新。 TLS 是传输中数据所需的最高安全级别的环境的最佳选择。

TLS 工作原理的简要概述

TLS 基于公钥基础结构 (PKI) 构建,该基础结构使用公钥/私钥对启用数据加密和建立数据完整性,并使用 X.509 证书进行身份验证。

许多安全协议(如 Kerberos v5 协议)依赖于一个密钥来加密和解密数据。 因此,此类协议取决于加密密钥的安全交换;在 Kerberos 协议中,这是通过从密钥分发中心 (KDC) 获取的票证完成的。 这要求使用 Kerberos 协议的每个人都注册到 KDC,这对电子商务 Web 服务器来说是一个不切实际的限制,该服务器旨在吸引来自世界各地的数百万客户。 因此,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 作为代理上的身份验证服务,COM 会将模拟级别设置为模拟,而不考虑进程默认值。 为了模拟在 TLS 中正常工作,客户端必须向服务器提供 X.509 证书,并且服务器必须具有该证书映射到服务器上的特定用户帐户。 有关详细信息,请参阅将 证书映射到用户帐户的分步指南

TLS 不支持 遮盖。 如果在 CoInitializeSecurityIClientSecurity::SetBlanket 调用中指定了隐藏标志和 TLS,将返回E_INVALIDARG。

TLS 不适用于设置为 None 的身份验证级别。 客户端和服务器之间的握手检查每个客户端和服务器设置的身份验证级别,并为连接选择更高的安全设置。

可以通过调用 CoInitializeSecurityCoSetProxyBlanket 来设置 TLS 的安全参数。 以下部分介绍进行这些调用所涉及的细微差别。

服务器如何设置安全毯

如果服务器想要使用 TLS,则必须在 CoInitializeSecurityasAuthSvc 参数中将 Schannel (RPC_C_AUTHN_GSS_SCHANNEL) 指定为身份验证服务。 若要防止客户端使用不太安全的身份验证服务连接到服务器,服务器应在调用 CoInitializeSecurity 时仅将 Schannel 指定为身份验证服务。 服务器在调用 CoInitializeSecurity 后无法更改安全毯子。

若要使用 TLS,应在服务器调用 CoInitializeSecurity 时指定以下参数:

  • pVoid 应该是指向 IAccessControl 对象的指针或指向 SECURITY_DESCRIPTOR的指针。 它不应为 NULL 或指向 AppID 的指针。
  • cAuthSvc 不能为 0 或 -1。 当 cAuthSvcis -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 设置 Processwide Security

客户端如何设置安全毛毯

如果客户端想要使用 TLS,则必须在 CoInitializeSecurity的 pAuthList 参数的身份验证服务列表中指定 Schannel (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 设置 Processwide Security

客户端如何更改安全毛毯

如果客户端想要使用 TLS,但在调用 CoInitializeSecurity 后更改安全毛毯,则必须调用 CoSetProxyBlanketIClientSecurity::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 的详细信息,请参阅 在接口代理级别设置安全性

示例:客户端更改安全毛毯

以下示例演示了客户端如何更改安全毛毯,以容纳来自服务器的请求,以便客户端提供其 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 和安全包